Merge branch 'main' into project-panel2

This commit is contained in:
Max Brunsfeld 2023-11-13 11:26:51 -08:00
commit 1968becf94
47 changed files with 4485 additions and 3457 deletions

57
Cargo.lock generated
View File

@ -1275,11 +1275,10 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
dependencies = [
"jobserver",
"libc",
]
@ -1880,6 +1879,30 @@ dependencies = [
"zed-actions",
]
[[package]]
name = "command_palette2"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"ctor",
"editor2",
"env_logger 0.9.3",
"fuzzy2",
"gpui2",
"language2",
"picker2",
"project2",
"serde",
"serde_json",
"settings2",
"theme2",
"ui2",
"util",
"workspace2",
"zed_actions2",
]
[[package]]
name = "component_test"
version = "0.1.0"
@ -2781,6 +2804,7 @@ dependencies = [
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript",
"ui2",
"unindent",
"util",
"workspace2",
@ -4370,15 +4394,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
[[package]]
name = "journal"
version = "0.1.0"
@ -4433,6 +4448,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json_comments"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105"
[[package]]
name = "jwt"
version = "0.16.0"
@ -6129,6 +6150,7 @@ dependencies = [
"serde_json",
"settings2",
"theme2",
"ui2",
"util",
]
@ -9186,6 +9208,7 @@ dependencies = [
"convert_case 0.6.0",
"gpui2",
"indexmap 1.9.3",
"json_comments",
"log",
"rust-embed",
"serde",
@ -11394,6 +11417,7 @@ dependencies = [
"cli",
"client2",
"collections",
"command_palette2",
"copilot2",
"ctor",
"db2",
@ -11481,6 +11505,15 @@ dependencies = [
"util",
"uuid 1.4.1",
"workspace2",
"zed_actions2",
]
[[package]]
name = "zed_actions2"
version = "0.1.0"
dependencies = [
"gpui2",
"serde",
]
[[package]]

View File

@ -20,6 +20,7 @@ members = [
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/command_palette2",
"crates/component_test",
"crates/context_menu",
"crates/copilot",
@ -111,7 +112,8 @@ members = [
"crates/xtask",
"crates/zed",
"crates/zed2",
"crates/zed-actions"
"crates/zed-actions",
"crates/zed_actions2"
]
default-members = ["crates/zed"]
resolver = "2"

View File

@ -10,6 +10,7 @@
"bindings": {
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"cmd-d": "editor::DuplicateLine",
"cmd-backspace": "editor::DeleteLine",
"cmd-pagedown": "editor::MovePageDown",
@ -18,7 +19,7 @@
"cmd-alt-enter": "editor::NewlineAbove",
"shift-enter": "editor::NewlineBelow",
"cmd--": "editor::Fold",
"cmd-=": "editor::UnfoldLines",
"cmd-+": "editor::UnfoldLines",
"alt-shift-g": "editor::SplitSelectionIntoLines",
"ctrl-g": [
"editor::SelectNext",

View File

@ -209,7 +209,7 @@
"ensure_final_newline_on_save": true,
// Whether or not to perform a buffer format before saving
"format_on_save": "on",
// How to perform a buffer format. This setting can take two values:
// How to perform a buffer format. This setting can take 4 values:
//
// 1. Format code using the current language server:
// "formatter": "language_server"

View File

@ -285,7 +285,7 @@
"name": "Inherited class",
"scope": "entity.other.inherited-class",
"settings": {
"foreground": "#D50D50"
"foreground": "#D50"
}
},
{
@ -452,7 +452,7 @@
"entity.other.attribute-name.pseudo-class"
],
"settings": {
"foreground": "#D50D50"
"foreground": "#D50"
}
},
{
@ -495,7 +495,7 @@
"name": "Markup link",
"scope": "markup.underline.link",
"settings": {
"foreground": "#D50D50"
"foreground": "#D50"
}
},
{

View File

@ -0,0 +1,34 @@
[package]
name = "command_palette2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/command_palette.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { package = "editor2", path = "../editor2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" }
picker = { package = "picker2", path = "../picker2" }
project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" }
ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package="workspace2", path = "../workspace2" }
zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
anyhow.workspace = true
serde.workspace = true
[dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
language = { package="language2", path = "../language2", features = ["test-support"] }
project = { package="project2", path = "../project2", features = ["test-support"] }
serde_json.workspace = true
workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View File

@ -0,0 +1,541 @@
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, div, Action, AppContext, Component, Div, EventEmitter, FocusHandle, Keystroke,
ParentElement, Render, StatelessInteractive, Styled, View, ViewContext, VisualContext,
WeakView, WindowContext,
};
use picker::{Picker, PickerDelegate};
use std::{
cmp::{self, Reverse},
sync::Arc,
};
use theme::ActiveTheme;
use ui::{v_stack, HighlightedLabel, StyledExt};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
};
use workspace::{Modal, ModalEvent, Workspace};
use zed_actions::OpenZedURL;
actions!(Toggle);
pub fn init(cx: &mut AppContext) {
cx.set_global(HitCounts::default());
cx.observe_new_views(CommandPalette::register).detach();
}
pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
}
impl CommandPalette {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &Toggle, cx| {
let Some(previous_focus_handle) = cx.focused() else {
return;
};
workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx));
});
}
fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
let filter = cx.try_global::<CommandPaletteFilter>();
let commands = cx
.available_actions()
.into_iter()
.filter_map(|action| {
let name = action.name();
let namespace = name.split("::").next().unwrap_or("malformed action name");
if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) {
return None;
}
Some(Command {
name: humanize_action_name(&name),
action,
keystrokes: vec![], // todo!()
})
})
.collect();
let delegate =
CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
Self { picker }
}
}
impl EventEmitter<ModalEvent> for CommandPalette {}
impl Modal for CommandPalette {
fn focus(&self, cx: &mut WindowContext) {
self.picker.update(cx, |picker, cx| picker.focus(cx));
}
}
impl Render for CommandPalette {
type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w_96().child(self.picker.clone())
}
}
pub type CommandPaletteInterceptor =
Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
pub struct CommandInterceptResult {
pub action: Box<dyn Action>,
pub string: String,
pub positions: Vec<usize>,
}
pub struct CommandPaletteDelegate {
command_palette: WeakView<CommandPalette>,
commands: Vec<Command>,
matches: Vec<StringMatch>,
selected_ix: usize,
previous_focus_handle: FocusHandle,
}
struct Command {
name: String,
action: Box<dyn Action>,
keystrokes: Vec<Keystroke>,
}
impl Clone for Command {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
}
}
}
/// Hit count for each command in the palette.
/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
#[derive(Default)]
struct HitCounts(HashMap<String, usize>);
impl CommandPaletteDelegate {
fn new(
command_palette: WeakView<CommandPalette>,
commands: Vec<Command>,
previous_focus_handle: FocusHandle,
) -> Self {
Self {
command_palette,
matches: commands
.iter()
.enumerate()
.map(|(i, command)| StringMatch {
candidate_id: i,
string: command.name.clone(),
positions: Vec::new(),
score: 0.0,
})
.collect(),
commands,
selected_ix: 0,
previous_focus_handle,
}
}
}
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = Div<Picker<Self>>;
fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_ix
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_ix = ix;
}
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let mut commands = self.commands.clone();
cx.spawn(move |picker, mut cx| async move {
cx.read_global::<HitCounts, _>(|hit_counts, _| {
commands.sort_by_key(|action| {
(
Reverse(hit_counts.0.get(&action.name).cloned()),
action.name.clone(),
)
});
})
.ok();
let candidates = commands
.iter()
.enumerate()
.map(|(ix, command)| StringMatchCandidate {
id: ix,
string: command.name.to_string(),
char_bag: command.name.chars().collect(),
})
.collect::<Vec<_>>();
let mut matches = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.0,
})
.collect()
} else {
fuzzy::match_strings(
&candidates,
&query,
true,
10000,
&Default::default(),
cx.background_executor().clone(),
)
.await
};
let mut intercept_result = cx
.try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
(interceptor)(&query, cx)
})
.flatten();
if *RELEASE_CHANNEL == ReleaseChannel::Dev {
if parse_zed_link(&query).is_some() {
intercept_result = Some(CommandInterceptResult {
action: OpenZedURL { url: query.clone() }.boxed_clone(),
string: query.clone(),
positions: vec![],
})
}
}
if let Some(CommandInterceptResult {
action,
string,
positions,
}) = intercept_result
{
if let Some(idx) = matches
.iter()
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
{
matches.remove(idx);
}
commands.push(Command {
name: string.clone(),
action,
keystrokes: vec![],
});
matches.insert(
0,
StringMatch {
candidate_id: commands.len() - 1,
string,
positions,
score: 0.0,
},
)
}
picker
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
delegate.commands = commands;
delegate.matches = matches;
if delegate.matches.is_empty() {
delegate.selected_ix = 0;
} else {
delegate.selected_ix =
cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
}
})
.log_err();
})
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.command_palette
.update(cx, |_, cx| cx.emit(ModalEvent::Dismissed))
.log_err();
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if self.matches.is_empty() {
self.dismissed(cx);
return;
}
let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix);
cx.update_global(|hit_counts: &mut HitCounts, _| {
*hit_counts.0.entry(command.name).or_default() += 1;
});
let action = command.action;
cx.focus(&self.previous_focus_handle);
cx.dispatch_action(action);
self.dismissed(cx);
}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
let Some(r#match) = self.matches.get(ix) else {
return div();
};
let Some(command) = self.commands.get(r#match.candidate_id) else {
return div();
};
div()
.px_1()
.text_color(colors.text)
.text_ui()
.bg(colors.ghost_element_background)
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
}
// fn render_match(
// &self,
// ix: usize,
// mouse_state: &mut MouseState,
// selected: bool,
// cx: &gpui::AppContext,
// ) -> AnyElement<Picker<Self>> {
// let mat = &self.matches[ix];
// let command = &self.actions[mat.candidate_id];
// let theme = theme::current(cx);
// let style = theme.picker.item.in_state(selected).style_for(mouse_state);
// let key_style = &theme.command_palette.key.in_state(selected);
// let keystroke_spacing = theme.command_palette.keystroke_spacing;
// Flex::row()
// .with_child(
// Label::new(mat.string.clone(), style.label.clone())
// .with_highlights(mat.positions.clone()),
// )
// .with_children(command.keystrokes.iter().map(|keystroke| {
// Flex::row()
// .with_children(
// [
// (keystroke.ctrl, "^"),
// (keystroke.alt, "⌥"),
// (keystroke.cmd, "⌘"),
// (keystroke.shift, "⇧"),
// ]
// .into_iter()
// .filter_map(|(modifier, label)| {
// if modifier {
// Some(
// Label::new(label, key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// } else {
// None
// }
// }),
// )
// .with_child(
// Label::new(keystroke.key.clone(), key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// .contained()
// .with_margin_left(keystroke_spacing)
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .into_any()
// }
}
fn humanize_action_name(name: &str) -> String {
let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count();
let mut result = String::with_capacity(capacity);
for char in name.chars() {
if char == ':' {
if result.ends_with(':') {
result.push(' ');
} else {
result.push(':');
}
} else if char == '_' {
result.push(' ');
} else if char.is_uppercase() {
if !result.ends_with(' ') {
result.push(' ');
}
result.extend(char.to_lowercase());
} else {
result.push(char);
}
}
result
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command")
.field("name", &self.name)
.field("keystrokes", &self.keystrokes)
.finish()
}
}
// #[cfg(test)]
// mod tests {
// use std::sync::Arc;
// use super::*;
// use editor::Editor;
// use gpui::{executor::Deterministic, TestAppContext};
// use project::Project;
// use workspace::{AppState, Workspace};
// #[test]
// fn test_humanize_action_name() {
// assert_eq!(
// humanize_action_name("editor::GoToDefinition"),
// "editor: go to definition"
// );
// assert_eq!(
// humanize_action_name("editor::Backspace"),
// "editor: backspace"
// );
// assert_eq!(
// humanize_action_name("go_to_line::Deploy"),
// "go to line: deploy"
// );
// }
// #[gpui::test]
// async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
// let app_state = init_test(cx);
// let project = Project::test(app_state.fs.clone(), [], cx).await;
// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// let workspace = window.root(cx);
// let editor = window.add_view(cx, |cx| {
// let mut editor = Editor::single_line(None, cx);
// editor.set_text("abc", cx);
// editor
// });
// workspace.update(cx, |workspace, cx| {
// cx.focus(&editor);
// workspace.add_item(Box::new(editor.clone()), cx)
// });
// workspace.update(cx, |workspace, cx| {
// toggle_command_palette(workspace, &Toggle, cx);
// });
// let palette = workspace.read_with(cx, |workspace, _| {
// workspace.modal::<CommandPalette>().unwrap()
// });
// palette
// .update(cx, |palette, cx| {
// // Fill up palette's command list by running an empty query;
// // we only need it to subsequently assert that the palette is initially
// // sorted by command's name.
// palette.delegate_mut().update_matches("".to_string(), cx)
// })
// .await;
// palette.update(cx, |palette, _| {
// let is_sorted =
// |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
// assert!(is_sorted(&palette.delegate().actions));
// });
// palette
// .update(cx, |palette, cx| {
// palette
// .delegate_mut()
// .update_matches("bcksp".to_string(), cx)
// })
// .await;
// palette.update(cx, |palette, cx| {
// assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
// palette.confirm(&Default::default(), cx);
// });
// deterministic.run_until_parked();
// editor.read_with(cx, |editor, cx| {
// assert_eq!(editor.text(cx), "ab");
// });
// // Add namespace filter, and redeploy the palette
// cx.update(|cx| {
// cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
// filter.filtered_namespaces.insert("editor");
// })
// });
// workspace.update(cx, |workspace, cx| {
// toggle_command_palette(workspace, &Toggle, cx);
// });
// // Assert editor command not present
// let palette = workspace.read_with(cx, |workspace, _| {
// workspace.modal::<CommandPalette>().unwrap()
// });
// palette
// .update(cx, |palette, cx| {
// palette
// .delegate_mut()
// .update_matches("bcksp".to_string(), cx)
// })
// .await;
// palette.update(cx, |palette, _| {
// assert!(palette.delegate().matches.is_empty())
// });
// }
// fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
// cx.update(|cx| {
// let app_state = AppState::test(cx);
// theme::init(cx);
// language::init(cx);
// editor::init(cx);
// workspace::init(app_state.clone(), cx);
// init(cx);
// Project::init_settings(cx);
// app_state
// })
// }
// }

View File

@ -171,10 +171,9 @@ impl ProjectDiagnosticsEditor {
.entry(*language_server_id)
.or_default()
.insert(path.clone());
let no_multiselections = this.editor.update(cx, |editor, cx| {
editor.selections.all::<usize>(cx).len() <= 1
});
if no_multiselections && !this.is_dirty(cx) {
if this.editor.read(cx).selections.all::<usize>(cx).is_empty()
&& !this.is_dirty(cx)
{
this.update_excerpts(Some(*language_server_id), cx);
}
}

View File

@ -44,6 +44,7 @@ snippet = { path = "../snippet" }
sum_tree = { path = "../sum_tree" }
text = { package="text2", path = "../text2" }
theme = { package="theme2", path = "../theme2" }
ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
sqlez = { path = "../sqlez" }
workspace = { package = "workspace2", path = "../workspace2" }

View File

@ -39,10 +39,12 @@ use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display;
use gpui::{
action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor,
Bounds, ClipboardItem, Context, DispatchContext, EventEmitter, FocusHandle, FontFeatures,
FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, Subscription,
Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext,
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@ -67,7 +69,7 @@ pub use multi_buffer::{
};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock};
use project::{FormatTrigger, Location, Project};
use project::{FormatTrigger, Location, Project, ProjectTransaction};
use rand::prelude::*;
use rpc::proto::*;
use scroll::{
@ -95,6 +97,7 @@ use text::{OffsetUtf16, Rope};
use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
};
use ui::{IconButton, StyledExt};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{
item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace,
@ -384,26 +387,6 @@ actions!(
UnfoldLines,
);
// impl_actions!(
// editor,
// [
// SelectNext,
// SelectPrevious,
// SelectAllMatches,
// SelectToBeginningOfLine,
// SelectToEndOfLine,
// ToggleCodeActions,
// MovePageUp,
// MovePageDown,
// ConfirmCompletion,
// ConfirmCodeAction,
// ToggleComments,
// FoldAt,
// UnfoldAt,
// GutterHover
// ]
// );
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
@ -919,15 +902,14 @@ impl ContextMenu {
fn render(
&self,
cursor_position: DisplayPoint,
style: EditorStyle,
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
todo!()
// match self {
// ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
// ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
// }
match self {
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
}
}
}
@ -940,29 +922,13 @@ struct CompletionsMenu {
match_candidates: Arc<[StringMatchCandidate]>,
matches: Arc<[StringMatch]>,
selected_item: usize,
list: UniformListState,
}
// todo!(this is fake)
#[derive(Clone, Default)]
struct UniformListState;
// todo!(this is fake)
impl UniformListState {
pub fn scroll_to(&mut self, target: ScrollTarget) {}
}
// todo!(this is somewhat fake)
#[derive(Debug)]
pub enum ScrollTarget {
Show(usize),
Center(usize),
scroll_handle: UniformListScrollHandle,
}
impl CompletionsMenu {
fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
self.selected_item = 0;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
self.attempt_resolve_selected_completion_documentation(project, cx);
cx.notify();
}
@ -973,7 +939,7 @@ impl CompletionsMenu {
} else {
self.selected_item = self.matches.len() - 1;
}
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
self.attempt_resolve_selected_completion_documentation(project, cx);
cx.notify();
}
@ -984,14 +950,14 @@ impl CompletionsMenu {
} else {
self.selected_item = 0;
}
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
self.attempt_resolve_selected_completion_documentation(project, cx);
cx.notify();
}
fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
self.selected_item = self.matches.len() - 1;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
self.attempt_resolve_selected_completion_documentation(project, cx);
cx.notify();
}
@ -1252,13 +1218,13 @@ impl CompletionsMenu {
fn render(
&self,
style: EditorStyle,
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) {
) -> AnyElement<Editor> {
todo!("old implementation below")
}
// ) -> AnyElement<Editor> {
// enum CompletionTag {}
// let settings = EditorSettings>(cx);
@ -1527,14 +1493,14 @@ struct CodeActionsMenu {
actions: Arc<[CodeAction]>,
buffer: Model<Buffer>,
selected_item: usize,
list: UniformListState,
scroll_handle: UniformListScrollHandle,
deployed_from_indicator: bool,
}
impl CodeActionsMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = 0;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
cx.notify()
}
@ -1544,7 +1510,7 @@ impl CodeActionsMenu {
} else {
self.selected_item = self.actions.len() - 1;
}
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
cx.notify();
}
@ -1554,13 +1520,13 @@ impl CodeActionsMenu {
} else {
self.selected_item = 0;
}
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
cx.notify();
}
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = self.actions.len() - 1;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
self.scroll_handle.scroll_to_item(self.selected_item);
cx.notify()
}
@ -1571,83 +1537,70 @@ impl CodeActionsMenu {
fn render(
&self,
mut cursor_position: DisplayPoint,
style: EditorStyle,
style: &EditorStyle,
cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
todo!("old version below")
let actions = self.actions.clone();
let selected_item = self.selected_item;
let element = uniform_list(
"code_actions_menu",
self.actions.len(),
move |editor, range, cx| {
actions[range.clone()]
.iter()
.enumerate()
.map(|(ix, action)| {
let item_ix = range.start + ix;
let selected = selected_item == item_ix;
let colors = cx.theme().colors();
div()
.px_2()
.text_ui()
.text_color(colors.text)
.when(selected, |style| {
style
.bg(colors.element_active)
.text_color(colors.text_accent)
})
.hover(|style| {
style
.bg(colors.element_hover)
.text_color(colors.text_accent)
})
.on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| {
cx.stop_propagation();
editor
.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
cx,
)
.map(|task| task.detach_and_log_err(cx));
})
.child(action.lsp_action.title.clone())
})
.collect()
},
)
.elevation_1(cx)
.px_2()
.py_1()
.with_width_from_item(
self.actions
.iter()
.enumerate()
.max_by_key(|(_, action)| action.lsp_action.title.chars().count())
.map(|(ix, _)| ix),
)
.render();
if self.deployed_from_indicator {
*cursor_position.column_mut() = 0;
}
(cursor_position, element)
}
// enum ActionTag {}
// let container_style = style.autocomplete.container;
// let actions = self.actions.clone();
// let selected_item = self.selected_item;
// let element = UniformList::new(
// self.list.clone(),
// actions.len(),
// cx,
// move |_, range, items, cx| {
// let start_ix = range.start;
// for (ix, action) in actions[range].iter().enumerate() {
// let item_ix = start_ix + ix;
// items.push(
// MouseEventHandler::new::<ActionTag, _>(item_ix, cx, |state, _| {
// let item_style = if item_ix == selected_item {
// style.autocomplete.selected_item
// } else if state.hovered() {
// style.autocomplete.hovered_item
// } else {
// style.autocomplete.item
// };
// Text::new(action.lsp_action.title.clone(), style.text.clone())
// .with_soft_wrap(false)
// .contained()
// .with_style(item_style)
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .on_down(MouseButton::Left, move |_, this, cx| {
// let workspace = this
// .workspace
// .as_ref()
// .and_then(|(workspace, _)| workspace.upgrade(cx));
// cx.window_context().defer(move |cx| {
// if let Some(workspace) = workspace {
// workspace.update(cx, |workspace, cx| {
// if let Some(task) = Editor::confirm_code_action(
// workspace,
// &ConfirmCodeAction {
// item_ix: Some(item_ix),
// },
// cx,
// ) {
// task.detach_and_log_err(cx);
// }
// });
// }
// });
// })
// .into_any(),
// );
// }
// },
// )
// .with_width_from_item(
// self.actions
// .iter()
// .enumerate()
// .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
// .map(|(ix, _)| ix),
// )
// .contained()
// .with_style(container_style)
// .into_any();
// if self.deployed_from_indicator {
// *cursor_position.column_mut() = 0;
// }
// (cursor_position, element)
// }
}
pub struct CopilotState {
@ -3660,7 +3613,7 @@ impl Editor {
completions: Arc::new(RwLock::new(completions.into())),
matches: Vec::new().into(),
selected_item: 0,
list: Default::default(),
scroll_handle: UniformListScrollHandle::new(),
};
menu.filter(query.as_deref(), cx.background_executor().clone())
.await;
@ -3846,156 +3799,161 @@ impl Editor {
// }))
// }
// pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
// let mut context_menu = self.context_menu.write();
// if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
// *context_menu = None;
// cx.notify();
// return;
// }
// drop(context_menu);
pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
let mut context_menu = self.context_menu.write();
if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
*context_menu = None;
cx.notify();
return;
}
drop(context_menu);
// let deployed_from_indicator = action.deployed_from_indicator;
// let mut task = self.code_actions_task.take();
// cx.spawn(|this, mut cx| async move {
// while let Some(prev_task) = task {
// prev_task.await;
// task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
// }
let deployed_from_indicator = action.deployed_from_indicator;
let mut task = self.code_actions_task.take();
cx.spawn(|this, mut cx| async move {
while let Some(prev_task) = task {
prev_task.await;
task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
}
// this.update(&mut cx, |this, cx| {
// if this.focused {
// if let Some((buffer, actions)) = this.available_code_actions.clone() {
// this.completion_tasks.clear();
// this.discard_copilot_suggestion(cx);
// *this.context_menu.write() =
// Some(ContextMenu::CodeActions(CodeActionsMenu {
// buffer,
// actions,
// selected_item: Default::default(),
// list: Default::default(),
// deployed_from_indicator,
// }));
// }
// }
// })?;
this.update(&mut cx, |this, cx| {
if this.focus_handle.is_focused(cx) {
if let Some((buffer, actions)) = this.available_code_actions.clone() {
this.completion_tasks.clear();
this.discard_copilot_suggestion(cx);
*this.context_menu.write() =
Some(ContextMenu::CodeActions(CodeActionsMenu {
buffer,
actions,
selected_item: Default::default(),
scroll_handle: UniformListScrollHandle::default(),
deployed_from_indicator,
}));
cx.notify();
}
}
})?;
// Ok::<_, anyhow::Error>(())
// })
// .detach_and_log_err(cx);
// }
Ok::<_, anyhow::Error>(())
})
.detach_and_log_err(cx);
}
// pub fn confirm_code_action(
// workspace: &mut Workspace,
// action: &ConfirmCodeAction,
// cx: &mut ViewContext<Workspace>,
// ) -> Option<Task<Result<()>>> {
// let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
// let actions_menu = if let ContextMenu::CodeActions(menu) =
// editor.update(cx, |editor, cx| editor.hide_context_menu(cx))?
// {
// menu
// } else {
// return None;
// };
// let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
// let action = actions_menu.actions.get(action_ix)?.clone();
// let title = action.lsp_action.title.clone();
// let buffer = actions_menu.buffer;
pub fn confirm_code_action(
&mut self,
action: &ConfirmCodeAction,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
menu
} else {
return None;
};
let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
let action = actions_menu.actions.get(action_ix)?.clone();
let title = action.lsp_action.title.clone();
let buffer = actions_menu.buffer;
let workspace = self.workspace()?;
// let apply_code_actions = workspace.project().clone().update(cx, |project, cx| {
// project.apply_code_action(buffer, action, true, cx)
// });
// let editor = editor.downgrade();
// Some(cx.spawn(|workspace, cx| async move {
// let project_transaction = apply_code_actions.await?;
// Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
// }))
// }
let apply_code_actions = workspace
.read(cx)
.project()
.clone()
.update(cx, |project, cx| {
project.apply_code_action(buffer, action, true, cx)
});
let workspace = workspace.downgrade();
Some(cx.spawn(|editor, cx| async move {
let project_transaction = apply_code_actions.await?;
Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
}))
}
// async fn open_project_transaction(
// this: &WeakViewHandle<Editor
// workspace: WeakViewHandle<Workspace
// transaction: ProjectTransaction,
// title: String,
// mut cx: AsyncAppContext,
// ) -> Result<()> {
// let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?;
async fn open_project_transaction(
this: &WeakView<Editor>,
workspace: WeakView<Workspace>,
transaction: ProjectTransaction,
title: String,
mut cx: AsyncWindowContext,
) -> Result<()> {
let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
// let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
// entries.sort_unstable_by_key(|(buffer, _)| {
// buffer.read_with(&cx, |buffer, _| buffer.file().map(|f| f.path().clone()))
// });
let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
cx.update(|_, cx| {
entries.sort_unstable_by_key(|(buffer, _)| {
buffer.read(cx).file().map(|f| f.path().clone())
});
})?;
// // If the project transaction's edits are all contained within this editor, then
// // avoid opening a new editor to display them.
// If the project transaction's edits are all contained within this editor, then
// avoid opening a new editor to display them.
// if let Some((buffer, transaction)) = entries.first() {
// if entries.len() == 1 {
// let excerpt = this.read_with(&cx, |editor, cx| {
// editor
// .buffer()
// .read(cx)
// .excerpt_containing(editor.selections.newest_anchor().head(), cx)
// })?;
// if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
// if excerpted_buffer == *buffer {
// let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
// let excerpt_range = excerpt_range.to_offset(buffer);
// buffer
// .edited_ranges_for_transaction::<usize>(transaction)
// .all(|range| {
// excerpt_range.start <= range.start
// && excerpt_range.end >= range.end
// })
// });
if let Some((buffer, transaction)) = entries.first() {
if entries.len() == 1 {
let excerpt = this.update(&mut cx, |editor, cx| {
editor
.buffer()
.read(cx)
.excerpt_containing(editor.selections.newest_anchor().head(), cx)
})?;
if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
if excerpted_buffer == *buffer {
let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
let excerpt_range = excerpt_range.to_offset(buffer);
buffer
.edited_ranges_for_transaction::<usize>(transaction)
.all(|range| {
excerpt_range.start <= range.start
&& excerpt_range.end >= range.end
})
})?;
// if all_edits_within_excerpt {
// return Ok(());
// }
// }
// }
// }
// } else {
// return Ok(());
// }
if all_edits_within_excerpt {
return Ok(());
}
}
}
}
} else {
return Ok(());
}
// let mut ranges_to_highlight = Vec::new();
// let excerpt_buffer = cx.build_model(|cx| {
// let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
// for (buffer_handle, transaction) in &entries {
// let buffer = buffer_handle.read(cx);
// ranges_to_highlight.extend(
// multibuffer.push_excerpts_with_context_lines(
// buffer_handle.clone(),
// buffer
// .edited_ranges_for_transaction::<usize>(transaction)
// .collect(),
// 1,
// cx,
// ),
// );
// }
// multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
// multibuffer
// });
let mut ranges_to_highlight = Vec::new();
let excerpt_buffer = cx.build_model(|cx| {
let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
for (buffer_handle, transaction) in &entries {
let buffer = buffer_handle.read(cx);
ranges_to_highlight.extend(
multibuffer.push_excerpts_with_context_lines(
buffer_handle.clone(),
buffer
.edited_ranges_for_transaction::<usize>(transaction)
.collect(),
1,
cx,
),
);
}
multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
multibuffer
})?;
// workspace.update(&mut cx, |workspace, cx| {
// let project = workspace.project().clone();
// let editor =
// cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
// workspace.add_item(Box::new(editor.clone()), cx);
// editor.update(cx, |editor, cx| {
// editor.highlight_background::<Self>(
// ranges_to_highlight,
// |theme| theme.editor.highlighted_line_background,
// cx,
// );
// });
// })?;
workspace.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let editor =
cx.build_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
workspace.add_item(Box::new(editor.clone()), cx);
editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
ranges_to_highlight,
|theme| theme.editor_highlighted_line_background,
cx,
);
});
})?;
// Ok(())
// }
Ok(())
}
fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let project = self.project.clone()?;
@ -4390,41 +4348,29 @@ impl Editor {
self.discard_copilot_suggestion(cx);
}
// pub fn render_code_actions_indicator(
// &self,
// style: &EditorStyle,
// is_active: bool,
// cx: &mut ViewContext<Self>,
// ) -> Option<AnyElement<Self>> {
// if self.available_code_actions.is_some() {
// enum CodeActions {}
// Some(
// MouseEventHandler::new::<CodeActions, _>(0, cx, |state, _| {
// Svg::new("icons/bolt.svg").with_color(
// style
// .code_actions
// .indicator
// .in_state(is_active)
// .style_for(state)
// .color,
// )
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .with_padding(Padding::uniform(3.))
// .on_down(MouseButton::Left, |_, this, cx| {
// this.toggle_code_actions(
// &ToggleCodeActions {
// deployed_from_indicator: true,
// },
// cx,
// );
// })
// .into_any(),
// )
// } else {
// None
// }
// }
pub fn render_code_actions_indicator(
&self,
style: &EditorStyle,
is_active: bool,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement<Self>> {
if self.available_code_actions.is_some() {
Some(
IconButton::new("code_actions_indicator", ui::Icon::Bolt)
.on_click(|editor: &mut Editor, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: true,
},
cx,
);
})
.render(),
)
} else {
None
}
}
// pub fn render_fold_indicators(
// &self,
@ -4491,29 +4437,27 @@ impl Editor {
// }
pub fn context_menu_visible(&self) -> bool {
false
// todo!("context menu")
// self.context_menu
// .read()
// .as_ref()
// .map_or(false, |menu| menu.visible())
self.context_menu
.read()
.as_ref()
.map_or(false, |menu| menu.visible())
}
// pub fn render_context_menu(
// &self,
// cursor_position: DisplayPoint,
// style: EditorStyle,
// cx: &mut ViewContext<Editor>,
// ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
// self.context_menu.read().as_ref().map(|menu| {
// menu.render(
// cursor_position,
// style,
// self.workspace.as_ref().map(|(w, _)| w.clone()),
// cx,
// )
// })
// }
pub fn render_context_menu(
&self,
cursor_position: DisplayPoint,
style: &EditorStyle,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, AnyElement<Editor>)> {
self.context_menu.read().as_ref().map(|menu| {
menu.render(
cursor_position,
style,
self.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
)
})
}
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
cx.notify();
@ -5954,29 +5898,29 @@ impl Editor {
});
}
// pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
// if let Some(context_menu) = self.context_menu.write().as_mut() {
// context_menu.select_first(self.project.as_ref(), cx);
// }
// }
pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_first(self.project.as_ref(), cx);
}
}
// pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
// if let Some(context_menu) = self.context_menu.write().as_mut() {
// context_menu.select_prev(self.project.as_ref(), cx);
// }
// }
pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_prev(self.project.as_ref(), cx);
}
}
// pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
// if let Some(context_menu) = self.context_menu.write().as_mut() {
// context_menu.select_next(self.project.as_ref(), cx);
// }
// }
pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_next(self.project.as_ref(), cx);
}
}
// pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
// if let Some(context_menu) = self.context_menu.write().as_mut() {
// context_menu.select_last(self.project.as_ref(), cx);
// }
// }
pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
if let Some(context_menu) = self.context_menu.write().as_mut() {
context_menu.select_last(self.project.as_ref(), cx);
}
}
pub fn move_to_previous_word_start(
&mut self,

View File

@ -1,3 +1,7 @@
use gpui::TestAppContext;
use language::language_settings::{AllLanguageSettings, AllLanguageSettingsContent};
use settings::SettingsStore;
// use super::*;
// use crate::{
// scroll::scroll_amount::ScrollAmount,
@ -8152,16 +8156,16 @@
// });
// }
// pub(crate) fn update_test_language_settings(
// cx: &mut TestAppContext,
// f: impl Fn(&mut AllLanguageSettingsContent),
// ) {
// cx.update(|cx| {
// cx.update_global::<SettingsStore, _, _>(|store, cx| {
// store.update_user_settings::<AllLanguageSettings>(cx, f);
// });
// });
// }
pub(crate) fn update_test_language_settings(
cx: &mut TestAppContext,
f: impl Fn(&mut AllLanguageSettingsContent),
) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, f);
});
});
}
// pub(crate) fn update_test_project_settings(
// cx: &mut TestAppContext,

View File

@ -15,7 +15,7 @@ use crate::{
use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement,
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
@ -447,7 +447,7 @@ impl EditorElement {
fn paint_gutter(
&mut self,
bounds: Bounds<Pixels>,
layout: &LayoutState,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
@ -495,14 +495,21 @@ impl EditorElement {
// }
// }
// todo!("code actions indicator")
// if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
// let mut x = 0.;
// let mut y = *row as f32 * line_height - scroll_top;
// x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.;
// y += (line_height - indicator.size().y) / 2.;
// indicator.paint(bounds.origin + point(x, y), visible_bounds, editor, cx);
// }
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height),
);
let indicator_size = indicator.element.measure(available_space, editor, cx);
let mut x = Pixels::ZERO;
let mut y = indicator.row as f32 * line_height - scroll_top;
// Center indicator.
x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
y += (line_height - indicator_size.height) / 2.;
indicator
.element
.draw(bounds.origin + point(x, y), available_space, editor, cx);
}
}
fn paint_diff_hunks(
@ -596,7 +603,7 @@ impl EditorElement {
fn paint_text(
&mut self,
bounds: Bounds<Pixels>,
layout: &LayoutState,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
@ -787,48 +794,46 @@ impl EditorElement {
)
}
cx.stack(0, |cx| {
cx.with_z_index(0, |cx| {
for cursor in cursors {
cursor.paint(content_origin, cx);
}
});
// cx.scene().push_layer(Some(bounds));
// cx.scene().pop_layer();
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
cx.with_z_index(1, |cx| {
let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(
(12. * line_height).min((bounds.size.height - line_height) / 2.),
),
);
let context_menu_size = context_menu.measure(available_space, editor, cx);
// if let Some((position, context_menu)) = layout.context_menu.as_mut() {
// cx.scene().push_stacking_context(None, None);
// let cursor_row_layout =
// &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
// let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
// let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
// let mut list_origin = content_origin + point(x, y);
// let list_width = context_menu.size().x;
// let list_height = context_menu.size().y;
let cursor_row_layout = &layout.position_map.line_layouts
[(position.row() - start_row) as usize]
.line;
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
let y =
(position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
let mut list_origin = content_origin + point(x, y);
let list_width = context_menu_size.width;
let list_height = context_menu_size.height;
// // Snap the right edge of the list to the right edge of the window if
// // its horizontal bounds overflow.
// if list_origin.x + list_width > cx.window_size().x {
// list_origin.set_x((cx.window_size().x - list_width).max(0.));
// }
// Snap the right edge of the list to the right edge of the window if
// its horizontal bounds overflow.
if list_origin.x + list_width > cx.viewport_size().width {
list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
}
// if list_origin.y + list_height > bounds.max_y {
// list_origin
// .set_y(list_origin.y - layout.position_map.line_height - list_height);
// }
if list_origin.y + list_height > bounds.lower_right().y {
list_origin.y -= layout.position_map.line_height - list_height;
}
// context_menu.paint(
// list_origin,
// Bounds::<Pixels>::from_points(
// gpui::Point::<Pixels>::zero(),
// point(f32::MAX, f32::MAX),
// ), // Let content bleed outside of editor
// editor,
// cx,
// );
// cx.scene().pop_stacking_context();
// }
context_menu.draw(list_origin, available_space, editor, cx);
})
}
// if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
// cx.scene().push_stacking_context(None, None);
@ -1774,26 +1779,28 @@ impl EditorElement {
snapshot = editor.snapshot(cx);
}
// todo!("context menu")
// let mut context_menu = None;
// let mut code_actions_indicator = None;
// if let Some(newest_selection_head) = newest_selection_head {
// if (start_row..end_row).contains(&newest_selection_head.row()) {
// if editor.context_menu_visible() {
// context_menu =
// editor.render_context_menu(newest_selection_head, style.clone(), cx);
// }
let mut context_menu = None;
let mut code_actions_indicator = None;
if let Some(newest_selection_head) = newest_selection_head {
if (start_row..end_row).contains(&newest_selection_head.row()) {
if editor.context_menu_visible() {
context_menu =
editor.render_context_menu(newest_selection_head, &self.style, cx);
}
// let active = matches!(
// editor.context_menu.read().as_ref(),
// Some(crate::ContextMenu::CodeActions(_))
// );
let active = matches!(
editor.context_menu.read().as_ref(),
Some(crate::ContextMenu::CodeActions(_))
);
// code_actions_indicator = editor
// .render_code_actions_indicator(&style, active, cx)
// .map(|indicator| (newest_selection_head.row(), indicator));
// }
// }
code_actions_indicator = editor
.render_code_actions_indicator(&style, active, cx)
.map(|element| CodeActionsIndicator {
row: newest_selection_head.row(),
element,
});
}
}
let visible_rows = start_row..start_row + line_layouts.len() as u32;
// todo!("hover")
@ -1831,18 +1838,6 @@ impl EditorElement {
// );
// }
// todo!("code actions")
// if let Some((_, indicator)) = code_actions_indicator.as_mut() {
// indicator.layout(
// SizeConstraint::strict_along(
// Axis::Vertical,
// line_height * style.code_actions.vertical_scale,
// ),
// editor,
// cx,
// );
// }
// todo!("fold indicators")
// for fold_indicator in fold_indicators.iter_mut() {
// if let Some(indicator) = fold_indicator.as_mut() {
@ -1941,8 +1936,8 @@ impl EditorElement {
display_hunks,
// blocks,
selections,
// context_menu,
// code_actions_indicator,
context_menu,
code_actions_indicator,
// fold_indicators,
tab_invisible,
space_invisible,
@ -2493,7 +2488,7 @@ impl Element<Editor> for EditorElement {
element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>,
) {
let layout = self.compute_layout(editor, cx, bounds);
let mut layout = self.compute_layout(editor, cx, bounds);
let gutter_bounds = Bounds {
origin: bounds.origin,
size: layout.gutter_size,
@ -2503,21 +2498,24 @@ impl Element<Editor> for EditorElement {
size: layout.text_size,
};
cx.with_content_mask(ContentMask { bounds }, |cx| {
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout.position_map,
cx,
);
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &layout, editor, cx);
}
self.paint_text(text_bounds, &layout, editor, cx);
let input_handler = ElementInputHandler::new(bounds, cx);
cx.handle_input(&editor.focus_handle, input_handler);
// We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |cx| {
cx.with_content_mask(ContentMask { bounds }, |cx| {
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout.position_map,
cx,
);
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
}
self.paint_text(text_bounds, &mut layout, editor, cx);
let input_handler = ElementInputHandler::new(bounds, cx);
cx.handle_input(&editor.focus_handle, input_handler);
});
});
}
}
@ -3143,14 +3141,19 @@ pub struct LayoutState {
show_scrollbars: bool,
is_singleton: bool,
max_row: u32,
// context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
// code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
code_actions_indicator: Option<CodeActionsIndicator>,
// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
// fold_indicators: Vec<Option<AnyElement<Editor>>>,
tab_invisible: Line,
space_invisible: Line,
}
struct CodeActionsIndicator {
row: u32,
element: AnyElement<Editor>,
}
struct PositionMap {
size: Size<Pixels>,
line_height: Pixels,
@ -4123,7 +4126,7 @@ fn build_key_listeners(
build_action_listener(Editor::unfold_at),
build_action_listener(Editor::fold_selected_ranges),
build_action_listener(Editor::show_completions),
// build_action_listener(Editor::toggle_code_actions), todo!()
build_action_listener(Editor::toggle_code_actions),
// build_action_listener(Editor::open_excerpts), todo!()
build_action_listener(Editor::toggle_soft_wrap),
build_action_listener(Editor::toggle_inlay_hints),
@ -4139,13 +4142,21 @@ fn build_key_listeners(
build_action_listener(Editor::restart_language_server),
build_action_listener(Editor::show_character_palette),
// build_action_listener(Editor::confirm_completion), todo!()
// build_action_listener(Editor::confirm_code_action), todo!()
build_action_listener(|editor, action, cx| {
editor
.confirm_code_action(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
// build_action_listener(Editor::rename), todo!()
// build_action_listener(Editor::confirm_rename), todo!()
// build_action_listener(Editor::find_all_references), todo!()
build_action_listener(Editor::next_copilot_suggestion),
build_action_listener(Editor::previous_copilot_suggestion),
build_action_listener(Editor::copilot_suggest),
build_action_listener(Editor::context_menu_first),
build_action_listener(Editor::context_menu_prev),
build_action_listener(Editor::context_menu_next),
build_action_listener(Editor::context_menu_last),
build_key_listener(
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
if phase == DispatchPhase::Bubble {

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,18 @@ pub enum ScrollAmount {
impl ScrollAmount {
pub fn lines(&self, editor: &mut Editor) -> f32 {
todo!()
// match self {
// Self::Line(count) => *count,
// Self::Page(count) => editor
// .visible_line_count()
// .map(|mut l| {
// // for full pages subtract one to leave an anchor line
// if count.abs() == 1.0 {
// l -= 1.0
// }
// (l * count).trunc()
// })
// .unwrap_or(0.),
// }
match self {
Self::Line(count) => *count,
Self::Page(count) => editor
.visible_line_count()
.map(|mut l| {
// for full pages subtract one to leave an anchor line
if count.abs() == 1.0 {
l -= 1.0
}
(l * count).trunc()
})
.unwrap_or(0.),
}
}
}

View File

@ -8,25 +8,12 @@ use text::{Bias, Point};
use theme::ActiveTheme;
use ui::{h_stack, modal, v_stack, Label, LabelColor};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::{ModalEvent, Workspace};
use workspace::{Modal, ModalEvent, Workspace};
actions!(Toggle);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
workspace
.modal_layer()
.register_modal(Toggle, |workspace, cx| {
let editor = workspace
.active_item(cx)
.and_then(|active_item| active_item.downcast::<Editor>())?;
Some(cx.build_view(|cx| GoToLine::new(editor, cx)))
});
},
)
.detach();
cx.observe_new_views(GoToLine::register).detach();
}
pub struct GoToLine {
@ -38,14 +25,28 @@ pub struct GoToLine {
}
impl EventEmitter<ModalEvent> for GoToLine {}
impl Modal for GoToLine {
fn focus(&self, cx: &mut WindowContext) {
self.line_editor.update(cx, |editor, cx| editor.focus(cx))
}
}
impl GoToLine {
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
let line_editor = cx.build_view(|cx| {
let editor = Editor::single_line(cx);
editor.focus(cx);
editor
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &Toggle, cx| {
let Some(editor) = workspace
.active_item(cx)
.and_then(|active_item| active_item.downcast::<Editor>())
else {
return;
};
workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
});
}
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
let line_editor = cx.build_view(|cx| Editor::single_line(cx));
let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
let editor = active_editor.read(cx);
@ -123,10 +124,6 @@ impl GoToLine {
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.active_editor.update(cx, |editor, cx| {
editor.focus(cx);
cx.notify();
});
cx.emit(ModalEvent::Dismissed);
}

View File

@ -67,14 +67,21 @@ impl<V: 'static> Flex<V> {
where
Tag: 'static,
{
// Don't assume that this initialization is what scroll_state really is in other panes:
// `element_state` is shared and there could be init races.
let scroll_state = cx.element_state::<Tag, Rc<ScrollState>>(
element_id,
Rc::new(ScrollState {
scroll_to: Cell::new(scroll_to),
scroll_position: Default::default(),
type_tag: TypeTag::new::<Tag>(),
scroll_to: Default::default(),
scroll_position: Default::default(),
}),
);
// Set scroll_to separately, because the default state is already picked as `None` by other panes
// by the time we start setting it here, hence update all others' state too.
scroll_state.update(cx, |this, _| {
this.scroll_to.set(scroll_to);
});
self.scroll_state = Some((scroll_state, cx.handle().id()));
self
}

View File

@ -4,7 +4,7 @@ use collections::{HashMap, HashSet};
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
use std::any::{type_name, Any};
use std::any::{type_name, Any, TypeId};
/// Actions are used to implement keyboard-driven UI.
/// When you declare an action, you can bind keys to the action in the keymap and
@ -100,6 +100,21 @@ where
}
}
impl dyn Action {
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
pub fn name(&self) -> SharedString {
ACTION_REGISTRY
.read()
.names_by_type_id
.get(&self.type_id())
.expect("type is not a registered action")
.clone()
}
}
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
lazy_static! {
@ -109,6 +124,7 @@ lazy_static! {
#[derive(Default)]
struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
names_by_type_id: HashMap<TypeId, SharedString>,
all_names: Vec<SharedString>, // So we can return a static slice.
}
@ -117,9 +133,24 @@ pub fn register_action<A: Action>() {
let name = A::qualified_name();
let mut lock = ACTION_REGISTRY.write();
lock.builders_by_name.insert(name.clone(), A::build);
lock.names_by_type_id
.insert(TypeId::of::<A>(), name.clone());
lock.all_names.push(name);
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();
let name = lock
.names_by_type_id
.get(type_id)
.ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
.clone();
drop(lock);
build_action(&name, None)
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();

View File

@ -1,4 +1,6 @@
use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, ViewContext};
use crate::{
AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, mem};
@ -61,6 +63,19 @@ trait ElementObject<V> {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
);
}
struct RenderedElement<V: 'static, E: Element<V>> {
@ -79,6 +94,11 @@ enum ElementRenderPhase<V> {
layout_id: LayoutId,
frame_state: Option<V>,
},
LayoutComputed {
layout_id: LayoutId,
available_space: Size<AvailableSpace>,
frame_state: Option<V>,
},
Painted,
}
@ -135,7 +155,9 @@ where
}
}
ElementRenderPhase::Start => panic!("must call initialize before layout"),
ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => {
ElementRenderPhase::LayoutRequested { .. }
| ElementRenderPhase::LayoutComputed { .. }
| ElementRenderPhase::Painted => {
panic!("element rendered twice")
}
};
@ -152,6 +174,11 @@ where
ElementRenderPhase::LayoutRequested {
layout_id,
mut frame_state,
}
| ElementRenderPhase::LayoutComputed {
layout_id,
mut frame_state,
..
} => {
let bounds = cx.layout_bounds(layout_id);
if let Some(id) = self.element.id() {
@ -171,6 +198,65 @@ where
_ => panic!("must call layout before paint"),
};
}
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
) -> Size<Pixels> {
if matches!(&self.phase, ElementRenderPhase::Start) {
self.initialize(view_state, cx);
}
if matches!(&self.phase, ElementRenderPhase::Initialized { .. }) {
self.layout(view_state, cx);
}
let layout_id = match &mut self.phase {
ElementRenderPhase::LayoutRequested {
layout_id,
frame_state,
} => {
cx.compute_layout(*layout_id, available_space);
let layout_id = *layout_id;
self.phase = ElementRenderPhase::LayoutComputed {
layout_id,
available_space,
frame_state: frame_state.take(),
};
layout_id
}
ElementRenderPhase::LayoutComputed {
layout_id,
available_space: prev_available_space,
..
} => {
if available_space != *prev_available_space {
cx.compute_layout(*layout_id, available_space);
*prev_available_space = available_space;
}
*layout_id
}
_ => panic!("cannot measure after painting"),
};
cx.layout_bounds(layout_id).size
}
fn draw(
&mut self,
mut origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
) {
self.measure(available_space, view_state, cx);
// Ignore the element offset when drawing this element, as the origin is already specified
// in absolute terms.
origin -= cx.element_offset();
cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx))
}
}
pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
@ -196,6 +282,27 @@ impl<V> AnyElement<V> {
pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
self.0.paint(view_state, cx)
}
/// Initializes this element and performs layout within the given available space to determine its size.
pub fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
) -> Size<Pixels> {
self.0.measure(available_space, view_state, cx)
}
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
pub fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
) {
self.0.draw(origin, available_space, view_state, cx)
}
}
pub trait Component<V> {

View File

@ -101,7 +101,12 @@ impl<V: 'static> Element<V> for Text<V> {
.map(|line| line.wrap_count() + 1)
.sum::<usize>();
let size = Size {
width: lines.iter().map(|line| line.layout.width).max().unwrap(),
width: lines
.iter()
.map(|line| line.layout.width)
.max()
.unwrap()
.ceil(),
height: line_height * line_count,
};

View File

@ -1,6 +1,6 @@
use crate::{
point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,
ElementId, ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
StyleRefinement, Styled, ViewContext,
};
@ -9,6 +9,9 @@ use smallvec::SmallVec;
use std::{cmp, ops::Range, sync::Arc};
use taffy::style::Overflow;
/// uniform_list provides lazy rendering for a set of items that are of uniform height.
/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
/// uniform_list will only render the visibile subset of items.
pub fn uniform_list<Id, V, C>(
id: Id,
item_count: usize,
@ -20,10 +23,14 @@ where
C: Component<V>,
{
let id = id.into();
let mut style = StyleRefinement::default();
style.overflow.y = Some(Overflow::Hidden);
UniformList {
id: id.clone(),
style: Default::default(),
style,
item_count,
item_to_measure_index: 0,
render_items: Box::new(move |view, visible_range, cx| {
f(view, visible_range, cx)
.into_iter()
@ -39,6 +46,7 @@ pub struct UniformList<V: 'static> {
id: ElementId,
style: StyleRefinement,
item_count: usize,
item_to_measure_index: usize,
render_items: Box<
dyn for<'a> Fn(
&'a mut V,
@ -50,7 +58,7 @@ pub struct UniformList<V: 'static> {
scroll_handle: Option<UniformListScrollHandle>,
}
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
#[derive(Clone, Debug)]
@ -86,8 +94,14 @@ impl<V: 'static> Styled for UniformList<V> {
}
}
#[derive(Default)]
pub struct UniformListState {
interactive: InteractiveElementState,
item_size: Size<Pixels>,
}
impl<V: 'static> Element<V> for UniformList<V> {
type ElementState = InteractiveElementState;
type ElementState = UniformListState;
fn id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
@ -95,20 +109,47 @@ impl<V: 'static> Element<V> for UniformList<V> {
fn initialize(
&mut self,
_: &mut V,
view_state: &mut V,
element_state: Option<Self::ElementState>,
_: &mut ViewContext<V>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
element_state.unwrap_or_default()
element_state.unwrap_or_else(|| {
let item_size = self.measure_item(view_state, None, cx);
UniformListState {
interactive: InteractiveElementState::default(),
item_size,
}
})
}
fn layout(
&mut self,
_view_state: &mut V,
_element_state: &mut Self::ElementState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) -> LayoutId {
cx.request_layout(&self.computed_style(), None)
let max_items = self.item_count;
let item_size = element_state.item_size;
let rem_size = cx.rem_size();
cx.request_measured_layout(
self.computed_style(),
rem_size,
move |known_dimensions: Size<Option<Pixels>>, available_space: Size<AvailableSpace>| {
let desired_height = item_size.height * max_items;
let width = known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
});
let height = match available_space.height {
AvailableSpace::Definite(x) => desired_height.min(x),
AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
};
size(width, height)
},
)
}
fn paint(
@ -119,7 +160,6 @@ impl<V: 'static> Element<V> for UniformList<V> {
cx: &mut ViewContext<V>,
) {
let style = self.computed_style();
style.paint(bounds, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
@ -131,14 +171,18 @@ impl<V: 'static> Element<V> for UniformList<V> {
);
cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
style.paint(bounds, cx);
let content_size;
if self.item_count > 0 {
let item_height = self.measure_item_height(view_state, padded_bounds, cx);
let item_height = self
.measure_item(view_state, Some(padded_bounds.size.width), cx)
.height;
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.lock().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
scroll_offset: element_state.track_scroll_offset(),
scroll_offset: element_state.interactive.track_scroll_offset(),
});
}
let visible_item_count = if item_height > px(0.) {
@ -147,6 +191,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
0
};
let scroll_offset = element_state
.interactive
.scroll_offset()
.map_or((0.0).into(), |offset| offset.y);
let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
@ -165,19 +210,13 @@ impl<V: 'static> Element<V> for UniformList<V> {
cx.with_z_index(1, |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
item.initialize(view_state, cx);
let layout_id = item.layout(view_state, cx);
cx.compute_layout(
layout_id,
Size {
width: AvailableSpace::Definite(bounds.size.width),
height: AvailableSpace::Definite(item_height),
},
);
let offset =
let item_origin =
padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, view_state, cx);
}
});
} else {
@ -190,33 +229,44 @@ impl<V: 'static> Element<V> for UniformList<V> {
let overflow = point(style.overflow.x, Overflow::Scroll);
cx.with_z_index(0, |cx| {
self.interactivity
.paint(bounds, content_size, overflow, element_state, cx);
self.interactivity.paint(
bounds,
content_size,
overflow,
&mut element_state.interactive,
cx,
);
});
})
}
}
impl<V> UniformList<V> {
fn measure_item_height(
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
self.item_to_measure_index = item_index.unwrap_or(0);
self
}
fn measure_item(
&self,
view_state: &mut V,
list_bounds: Bounds<Pixels>,
list_width: Option<Pixels>,
cx: &mut ViewContext<V>,
) -> Pixels {
let mut items = (self.render_items)(view_state, 0..1, cx);
debug_assert!(items.len() == 1);
) -> Size<Pixels> {
if self.item_count == 0 {
return Size::default();
}
let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx);
let mut item_to_measure = items.pop().unwrap();
item_to_measure.initialize(view_state, cx);
let layout_id = item_to_measure.layout(view_state, cx);
cx.compute_layout(
layout_id,
Size {
width: AvailableSpace::Definite(list_bounds.size.width),
height: AvailableSpace::MinContent,
},
let available_space = size(
list_width.map_or(AvailableSpace::MinContent, |width| {
AvailableSpace::Definite(width)
}),
AvailableSpace::MinContent,
);
cx.layout_bounds(layout_id).size.height
item_to_measure.measure(available_space, view_state, cx)
}
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {

View File

@ -785,6 +785,10 @@ impl Pixels {
Self(self.0.round())
}
pub fn ceil(&self) -> Self {
Self(self.0.ceil())
}
pub fn scale(&self, factor: f32) -> ScaledPixels {
ScaledPixels(self.0 * factor)
}

View File

@ -94,7 +94,6 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
fn on_mouse_down_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
@ -103,10 +102,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
self.stateless_interactivity()
.mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
&& event.button == button
&& !bounds.contains_point(&event.position)
{
if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
handler(view, event, cx)
}
}));

View File

@ -182,7 +182,8 @@ impl Platform for TestPlatform {
}
fn should_auto_hide_scrollbars(&self) -> bool {
unimplemented!()
// todo()
true
}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {

View File

@ -1,10 +1,14 @@
use std::{rc::Rc, sync::Arc};
use std::{
rc::Rc,
sync::{self, Arc},
};
use collections::HashMap;
use parking_lot::Mutex;
use crate::{
px, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point, Scene, Size,
WindowAppearance, WindowBounds, WindowOptions,
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
PlatformWindow, Point, Scene, Size, TileId, WindowAppearance, WindowBounds, WindowOptions,
};
#[derive(Default)]
@ -30,7 +34,7 @@ impl TestWindow {
current_scene: Default::default(),
display,
sprite_atlas: Arc::new(TestAtlas),
sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(),
}
}
@ -154,26 +158,71 @@ impl PlatformWindow for TestWindow {
self.current_scene.lock().replace(scene);
}
fn sprite_atlas(&self) -> std::sync::Arc<dyn crate::PlatformAtlas> {
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.sprite_atlas.clone()
}
}
pub struct TestAtlas;
pub struct TestAtlasState {
next_id: u32,
tiles: HashMap<AtlasKey, AtlasTile>,
}
pub struct TestAtlas(Mutex<TestAtlasState>);
impl TestAtlas {
pub fn new() -> Self {
TestAtlas(Mutex::new(TestAtlasState {
next_id: 0,
tiles: HashMap::default(),
}))
}
}
impl PlatformAtlas for TestAtlas {
fn get_or_insert_with<'a>(
&self,
_key: &crate::AtlasKey,
_build: &mut dyn FnMut() -> anyhow::Result<(
key: &crate::AtlasKey,
build: &mut dyn FnMut() -> anyhow::Result<(
Size<crate::DevicePixels>,
std::borrow::Cow<'a, [u8]>,
)>,
) -> anyhow::Result<crate::AtlasTile> {
todo!()
let mut state = self.0.lock();
if let Some(tile) = state.tiles.get(key) {
return Ok(tile.clone());
}
state.next_id += 1;
let texture_id = state.next_id;
state.next_id += 1;
let tile_id = state.next_id;
drop(state);
let (size, _) = build()?;
let mut state = self.0.lock();
state.tiles.insert(
key.clone(),
crate::AtlasTile {
texture_id: AtlasTextureId {
index: texture_id,
kind: crate::AtlasTextureKind::Path,
},
tile_id: TileId(tile_id),
bounds: crate::Bounds {
origin: Point::zero(),
size,
},
},
);
Ok(state.tiles[key].clone())
}
fn clear(&self) {
todo!()
let mut state = self.0.lock();
state.tiles = HashMap::default();
state.next_id = 0;
}
}

View File

@ -1,5 +1,6 @@
use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
use collections::HashMap;
use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::fmt::Debug;
use taffy::{
geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
@ -12,6 +13,7 @@ pub struct TaffyLayoutEngine {
taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: HashSet<LayoutId>,
}
static EXPECT_MESSAGE: &'static str =
@ -23,9 +25,17 @@ impl TaffyLayoutEngine {
taffy: Taffy::new(),
children_to_parents: HashMap::default(),
absolute_layout_bounds: HashMap::default(),
computed_layouts: HashSet::default(),
}
}
pub fn clear(&mut self) {
self.taffy.clear();
self.children_to_parents.clear();
self.absolute_layout_bounds.clear();
self.computed_layouts.clear();
}
pub fn request_layout(
&mut self,
style: &Style,
@ -115,6 +125,7 @@ impl TaffyLayoutEngine {
}
pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
// Leaving this here until we have a better instrumentation approach.
// println!("Laying out {} children", self.count_all_children(id)?);
// println!("Max layout depth: {}", self.max_depth(0, id)?);
@ -124,6 +135,22 @@ impl TaffyLayoutEngine {
// println!("N{} --> N{}", u64::from(a), u64::from(b));
// }
// println!("");
//
if !self.computed_layouts.insert(id) {
let mut stack = SmallVec::<[LayoutId; 64]>::new();
stack.push(id);
while let Some(id) = stack.pop() {
self.absolute_layout_bounds.remove(&id);
stack.extend(
self.taffy
.children(id.into())
.expect(EXPECT_MESSAGE)
.into_iter()
.map(Into::into),
);
}
}
// let started_at = std::time::Instant::now();
self.taffy
@ -397,7 +424,7 @@ where
}
}
#[derive(Copy, Clone, Default, Debug)]
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Definite(Pixels),

View File

@ -184,6 +184,10 @@ impl AnyView {
.compute_layout(layout_id, available_space);
(self.paint)(self, &mut rendered_element, cx);
}
pub(crate) fn draw_dispatch_stack(&self, cx: &mut WindowContext) {
(self.initialize)(self, cx);
}
}
impl<V: 'static> Component<V> for AnyView {

View File

@ -1,14 +1,15 @@
use crate::{
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow,
SharedString, Size, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline,
UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@ -145,6 +146,11 @@ impl FocusHandle {
}
}
/// Moves the focus to the element associated with this handle.
pub fn focus(&self, cx: &mut WindowContext) {
cx.focus(self)
}
/// Obtains whether the element associated with this handle is currently focused.
pub fn is_focused(&self, cx: &WindowContext) -> bool {
self.id.is_focused(cx)
@ -200,7 +206,7 @@ pub struct Window {
display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
content_size: Size<Pixels>,
viewport_size: Size<Pixels>,
pub(crate) layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
@ -227,7 +233,7 @@ pub(crate) struct Frame {
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
freeze_key_dispatch_stack: bool,
focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) scene_builder: SceneBuilder,
@ -299,7 +305,7 @@ impl Window {
display_id,
sprite_atlas,
rem_size: px(16.),
content_size,
viewport_size: content_size,
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
@ -326,7 +332,7 @@ impl Window {
/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
/// contexts when matching key events against the keymap. A key listener can be either an action
/// handler or a [KeyDown] / [KeyUp] event listener.
enum KeyDispatchStackFrame {
pub(crate) enum KeyDispatchStackFrame {
Listener {
event_type: TypeId,
listener: AnyKeyListener,
@ -401,11 +407,18 @@ impl<'a> WindowContext<'a> {
/// Move focus to the element associated with the given `FocusHandle`.
pub fn focus(&mut self, handle: &FocusHandle) {
if self.window.focus == Some(handle.id) {
return;
}
if self.window.last_blur.is_none() {
self.window.last_blur = Some(self.window.focus);
}
self.window.focus = Some(handle.id);
// self.window.current_frame.key_dispatch_stack.clear()
// self.window.root_view.initialize()
self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle,
focused: Some(handle.id),
@ -427,6 +440,14 @@ impl<'a> WindowContext<'a> {
self.notify();
}
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
self.defer(|cx| {
cx.app.propagate_event = true;
let stack = cx.dispatch_stack();
cx.dispatch_action_internal(action, &stack[..])
})
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
/// that are currently on the stack to be returned to the app.
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
@ -609,7 +630,7 @@ impl<'a> WindowContext<'a> {
fn window_bounds_changed(&mut self) {
self.window.scale_factor = self.window.platform_window.scale_factor();
self.window.content_size = self.window.platform_window.content_size();
self.window.viewport_size = self.window.platform_window.content_size();
self.window.bounds = self.window.platform_window.bounds();
self.window.display_id = self.window.platform_window.display().id();
self.window.dirty = true;
@ -624,6 +645,10 @@ impl<'a> WindowContext<'a> {
self.window.bounds
}
pub fn viewport_size(&self) -> Size<Pixels> {
self.window.viewport_size
}
pub fn is_window_active(&self) -> bool {
self.window.active
}
@ -717,7 +742,7 @@ impl<'a> WindowContext<'a> {
/// Called during painting to invoke the given closure in a new stacking context. The given
/// z-index is interpreted relative to the previous call to `stack`.
pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.current_frame.z_index_stack.push(z_index);
let result = f(self);
self.window.current_frame.z_index_stack.pop();
@ -1015,13 +1040,13 @@ impl<'a> WindowContext<'a> {
self.start_frame();
self.stack(0, |cx| {
let available_space = cx.window.content_size.map(Into::into);
self.with_z_index(0, |cx| {
let available_space = cx.window.viewport_size.map(Into::into);
root_view.draw(available_space, cx);
});
if let Some(active_drag) = self.app.active_drag.take() {
self.stack(1, |cx| {
self.with_z_index(1, |cx| {
let offset = cx.mouse_position() - active_drag.cursor_offset;
cx.with_element_offset(Some(offset), |cx| {
let available_space =
@ -1031,7 +1056,7 @@ impl<'a> WindowContext<'a> {
});
});
} else if let Some(active_tooltip) = self.app.active_tooltip.take() {
self.stack(1, |cx| {
self.with_z_index(1, |cx| {
cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
let available_space =
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
@ -1054,12 +1079,34 @@ impl<'a> WindowContext<'a> {
self.window.dirty = false;
}
pub(crate) fn dispatch_stack(&mut self) -> Vec<KeyDispatchStackFrame> {
let root_view = self.window.root_view.take().unwrap();
let window = &mut *self.window;
let mut spare_frame = Frame::default();
mem::swap(&mut spare_frame, &mut window.previous_frame);
self.start_frame();
root_view.draw_dispatch_stack(self);
let window = &mut *self.window;
// restore the old values of current and previous frame,
// putting the new frame into spare_frame.
mem::swap(&mut window.current_frame, &mut window.previous_frame);
mem::swap(&mut spare_frame, &mut window.previous_frame);
self.window.root_view = Some(root_view);
spare_frame.key_dispatch_stack
}
/// Rotate the current frame and the previous frame, then clear the current frame.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
self.text_system().start_frame();
let window = &mut *self.window;
window.layout_engine.clear();
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
frame.element_states.clear();
@ -1196,7 +1243,7 @@ impl<'a> WindowContext<'a> {
DispatchPhase::Capture,
self,
) {
self.dispatch_action(action, &key_dispatch_stack[..ix]);
self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
}
if !self.app.propagate_event {
break;
@ -1223,7 +1270,10 @@ impl<'a> WindowContext<'a> {
DispatchPhase::Bubble,
self,
) {
self.dispatch_action(action, &key_dispatch_stack[..ix]);
self.dispatch_action_internal(
action,
&key_dispatch_stack[..ix],
);
}
if !self.app.propagate_event {
@ -1295,7 +1345,29 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
fn dispatch_action(
pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
key_dispatch_stack.iter().filter_map(|frame| {
match frame {
// todo!factor out a KeyDispatchStackFrame::Action
KeyDispatchStackFrame::Listener {
event_type,
listener: _,
} => {
match build_action_from_type(event_type) {
Ok(action) => Some(action),
Err(err) => {
dbg!(err);
None
} // we'll hit his if TypeId == KeyDown
}
}
KeyDispatchStackFrame::Context(_) => None,
}
})
}
pub(crate) fn dispatch_action_internal(
&mut self,
action: Box<dyn Action>,
dispatch_stack: &[KeyDispatchStackFrame],
@ -1684,7 +1756,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
.unwrap_or_else(|| ContentMask {
bounds: Bounds {
origin: Point::default(),
size: self.window().content_size,
size: self.window().viewport_size,
},
})
}

View File

@ -10,6 +10,7 @@ doctest = false
[dependencies]
editor = { package = "editor2", path = "../editor2" }
ui = { package = "ui2", path = "../ui2" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
settings = { package = "settings2", path = "../settings2" }

View File

@ -4,7 +4,8 @@ use gpui::{
StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
WindowContext,
};
use std::cmp;
use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
@ -20,7 +21,7 @@ pub trait PickerDelegate: Sized + 'static {
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>;
fn placeholder_text(&self) -> Arc<str>;
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
@ -36,7 +37,11 @@ pub trait PickerDelegate: Sized + 'static {
impl<D: PickerDelegate> Picker<D> {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let editor = cx.build_view(|cx| Editor::single_line(cx));
let editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text(delegate.placeholder_text(), cx);
editor
});
cx.subscribe(&editor, Self::on_input_editor_event).detach();
Self {
delegate,
@ -57,6 +62,7 @@ impl<D: PickerDelegate> Picker<D> {
let ix = cmp::min(index + 1, count - 1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
cx.notify();
}
}
@ -67,6 +73,7 @@ impl<D: PickerDelegate> Picker<D> {
let ix = index.saturating_sub(1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
cx.notify();
}
}
@ -75,6 +82,7 @@ impl<D: PickerDelegate> Picker<D> {
if count > 0 {
self.delegate.set_selected_index(0, cx);
self.scroll_handle.scroll_to_item(0);
cx.notify();
}
}
@ -83,6 +91,7 @@ impl<D: PickerDelegate> Picker<D> {
if count > 0 {
self.delegate.set_selected_index(count - 1, cx);
self.scroll_handle.scroll_to_item(count - 1);
cx.notify();
}
}
@ -133,12 +142,13 @@ impl<D: PickerDelegate> Picker<D> {
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
.id("picker-container")
.focusable()
.size_full()
.elevation_2(cx)
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::select_first)
@ -146,18 +156,42 @@ impl<D: PickerDelegate> Render for Picker<D> {
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
.child(self.editor.clone())
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
.collect()
}
})
.track_scroll(self.scroll_handle.clone())
.size_full(),
v_stack()
.py_0p5()
.px_1()
.child(div().px_1().py_0p5().child(self.editor.clone())),
)
.child(Divider::horizontal())
.when(self.delegate.match_count() > 0, |el| {
el.child(
v_stack()
.p_1()
.grow()
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| {
this.delegate.render_match(ix, ix == selected_ix, cx)
})
.collect()
}
})
.track_scroll(self.scroll_handle.clone()),
)
.max_h_72()
.overflow_hidden(),
)
})
.when(self.delegate.match_count() == 0, |el| {
el.child(
v_stack()
.p_1()
.grow()
.child(Label::new("No matches").color(LabelColor::Muted)),
)
})
}
}

View File

@ -44,6 +44,10 @@ impl PickerDelegate for Delegate {
self.candidates.len()
}
fn placeholder_text(&self) -> Arc<str> {
"Test".into()
}
fn render_match(
&self,
ix: usize,

View File

@ -98,14 +98,14 @@ pub fn synthwave_84() -> UserThemeFamily {
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0xd50c50ff).into()),
color: Some(rgba(0xdd5500ff).into()),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0xd50c50ff).into()),
color: Some(rgba(0xdd5500ff).into()),
..Default::default()
},
),

View File

@ -11,6 +11,7 @@ anyhow.workspace = true
convert_case = "0.6.0"
gpui = { package = "gpui2", path = "../gpui2" }
indexmap = "1.6.2"
json_comments = "0.2.2"
log.workspace = true
rust-embed.workspace = true
serde.workspace = true

View File

@ -11,6 +11,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context, Result};
use convert_case::{Case, Casing};
use gpui::serde_json;
use json_comments::StripComments;
use log::LevelFilter;
use serde::Deserialize;
use simplelog::SimpleLogger;
@ -111,7 +112,8 @@ fn main() -> Result<()> {
}
};
let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_file)
let theme_without_comments = StripComments::new(theme_file);
let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_without_comments)
.context(format!("failed to parse theme {theme_file_path:?}"))?;
let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata);

View File

@ -3,6 +3,7 @@ mod button;
mod checkbox;
mod context_menu;
mod details;
mod divider;
mod elevated_surface;
mod facepile;
mod icon;
@ -31,6 +32,7 @@ pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
pub use details::*;
pub use divider::*;
pub use elevated_surface::*;
pub use facepile::*;
pub use icon::*;

View File

@ -0,0 +1,46 @@
use crate::prelude::*;
enum DividerDirection {
Horizontal,
Vertical,
}
#[derive(Component)]
pub struct Divider {
direction: DividerDirection,
inset: bool,
}
impl Divider {
pub fn horizontal() -> Self {
Self {
direction: DividerDirection::Horizontal,
inset: false,
}
}
pub fn vertical() -> Self {
Self {
direction: DividerDirection::Vertical,
inset: false,
}
}
pub fn inset(mut self) -> Self {
self.inset = true;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
}
DividerDirection::Vertical => {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(cx.theme().colors().border_variant)
}
}

View File

@ -24,5 +24,5 @@ pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<
}
pub fn modal<V>(cx: &mut ViewContext<V>) -> Div<V> {
elevated_surface(ElevationIndex::ModalSurfaces, cx)
elevated_surface(ElevationIndex::ModalSurface, cx)
}

View File

@ -98,6 +98,7 @@ impl<V: 'static> IconButton<V> {
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx);
});
}

View File

@ -401,7 +401,7 @@ impl List {
v_stack()
.w_full()
.py_1()
.children(self.header.map(|header| header))
.children(self.header)
.child(list_content)
}
}

View File

@ -34,9 +34,9 @@ Material Design 3 has a some great visualizations of elevation that may be helpf
The app background constitutes the lowest elevation layer, appearing behind all other surfaces and components. It is predominantly used for the background color of the app.
### UI Surface
### Surface
The UI Surface, located above the app background, is the standard level for all elements
The Surface elevation level, located above the app background, is the standard level for all elements
Example Elements: Title Bar, Panel, Tab Bar, Editor

View File

@ -11,43 +11,53 @@ pub enum Elevation {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElevationIndex {
AppBackground,
UISurface,
Background,
Surface,
ElevatedSurface,
Wash,
ModalSurfaces,
ModalSurface,
DraggedElement,
}
impl ElevationIndex {
pub fn z_index(self) -> u32 {
match self {
ElevationIndex::AppBackground => 0,
ElevationIndex::UISurface => 100,
ElevationIndex::Background => 0,
ElevationIndex::Surface => 100,
ElevationIndex::ElevatedSurface => 200,
ElevationIndex::Wash => 300,
ElevationIndex::ModalSurfaces => 400,
ElevationIndex::ModalSurface => 400,
ElevationIndex::DraggedElement => 900,
}
}
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
match self {
ElevationIndex::AppBackground => smallvec![],
ElevationIndex::Surface => smallvec![],
ElevationIndex::UISurface => smallvec![BoxShadow {
ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.12),
offset: point(px(0.), px(1.)),
blur_radius: px(3.),
spread_radius: px(0.),
}],
_ => smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.32),
offset: point(px(1.), px(3.)),
blur_radius: px(12.),
spread_radius: px(0.),
}],
ElevationIndex::ModalSurface => smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.12),
offset: point(px(0.), px(1.)),
blur_radius: px(3.),
spread_radius: px(0.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.16),
offset: point(px(3.), px(1.)),
blur_radius: px(12.),
spread_radius: px(0.),
},
],
_ => smallvec![],
}
}
}

View File

@ -1,33 +1,33 @@
use gpui::{Div, ElementFocus, ElementInteractivity, Styled};
use gpui::{Div, ElementFocus, ElementInteractivity, Styled, UniformList, ViewContext};
use theme2::ActiveTheme;
use crate::UITextSize;
use crate::{ElevationIndex, UITextSize};
fn elevated<E: Styled, V: 'static>(this: E, cx: &mut ViewContext<V>, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.rounded_lg()
.border()
.border_color(cx.theme().colors().border_variant)
.shadow(index.shadow())
}
/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
pub trait StyledExt: Styled {
pub trait StyledExt: Styled + Sized {
/// Horizontally stacks elements.
///
/// Sets `flex()`, `flex_row()`, `items_center()`
fn h_flex(self) -> Self
where
Self: Sized,
{
fn h_flex(self) -> Self {
self.flex().flex_row().items_center()
}
/// Vertically stacks elements.
///
/// Sets `flex()`, `flex_col()`
fn v_flex(self) -> Self
where
Self: Sized,
{
fn v_flex(self) -> Self {
self.flex().flex_col()
}
fn text_ui_size(self, size: UITextSize) -> Self
where
Self: Sized,
{
fn text_ui_size(self, size: UITextSize) -> Self {
let size = size.rems();
self.text_size(size)
@ -40,10 +40,7 @@ pub trait StyledExt: Styled {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use [`text_ui_sm`] for regular-sized text.
fn text_ui(self) -> Self
where
Self: Sized,
{
fn text_ui(self) -> Self {
let size = UITextSize::default().rems();
self.text_size(size)
@ -56,14 +53,44 @@ pub trait StyledExt: Styled {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use [`text_ui`] for regular-sized text.
fn text_ui_sm(self) -> Self
where
Self: Sized,
{
fn text_ui_sm(self) -> Self {
let size = UITextSize::Small.rems();
self.text_size(size)
}
/// The [`Surface`](ui2::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Example Elements: Title Bar, Panel, Tab Bar, Editor
fn elevation_1<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
elevated(self, cx, ElevationIndex::Surface)
}
/// Non-Modal Elevated Surfaces appear above the [`Surface`](ui2::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
fn elevation_2<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
elevated(self, cx, ElevationIndex::ElevatedSurface)
}
// There is no elevation 3, as the third elevation level is reserved for wash layers. See [`Elevation`](ui2::Elevation).
/// Modal Surfaces are used for elements that should appear above all other UI elements and are located above the wash layer. This is the maximum elevation at which UI elements can be rendered in their default state.
///
/// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
///
/// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui2::ElevationIndex::ElevatedSurface) layer.
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
/// Examples: Settings Modal, Channel Management, Wizards/Setup UI, Dialogs
fn elevation_4<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
elevated(self, cx, ElevationIndex::ModalSurface)
}
}
impl<V, I, F> StyledExt for Div<V, I, F>
@ -72,3 +99,5 @@ where
F: ElementFocus<V>,
{
}
impl<V> StyledExt for UniformList<V> {}

View File

@ -1,15 +1,22 @@
use crate::Workspace;
use gpui::{
div, px, AnyView, Component, Div, EventEmitter, ParentElement, Render, StatelessInteractive,
Styled, Subscription, View, ViewContext,
div, px, AnyView, Div, EventEmitter, FocusHandle, ParentElement, Render, StatelessInteractive,
Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
};
use std::{any::TypeId, sync::Arc};
use ui::v_stack;
pub struct ActiveModal {
modal: AnyView,
subscription: Subscription,
previous_focus_handle: Option<FocusHandle>,
focus_handle: FocusHandle,
}
pub struct ModalLayer {
open_modal: Option<AnyView>,
subscription: Option<Subscription>,
registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
active_modal: Option<ActiveModal>,
}
pub trait Modal: Render + EventEmitter<ModalEvent> {
fn focus(&self, cx: &mut WindowContext);
}
pub enum ModalEvent {
@ -18,74 +25,82 @@ pub enum ModalEvent {
impl ModalLayer {
pub fn new() -> Self {
Self {
open_modal: None,
subscription: None,
registered_modals: Vec::new(),
}
Self { active_modal: None }
}
pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
pub fn toggle_modal<V, B>(&mut self, cx: &mut ViewContext<Self>, build_view: B)
where
V: EventEmitter<ModalEvent> + Render,
B: Fn(&mut Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
V: Modal,
B: FnOnce(&mut ViewContext<V>) -> V,
{
let build_view = Arc::new(build_view);
let previous_focus = cx.focused();
self.registered_modals.push((
TypeId::of::<A>(),
Box::new(move |mut div| {
let build_view = build_view.clone();
if let Some(active_modal) = &self.active_modal {
let is_close = active_modal.modal.clone().downcast::<V>().is_ok();
self.hide_modal(cx);
if is_close {
return;
}
}
let new_modal = cx.build_view(build_view);
self.show_modal(new_modal, cx);
}
div.on_action(move |workspace, event: &A, cx| {
let Some(new_modal) = (build_view)(workspace, cx) else {
return;
};
workspace.modal_layer().show_modal(new_modal, cx);
})
pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>)
where
V: Modal,
{
self.active_modal = Some(ActiveModal {
modal: new_modal.clone().into(),
subscription: cx.subscribe(&new_modal, |this, modal, e, cx| match e {
ModalEvent::Dismissed => this.hide_modal(cx),
}),
));
}
pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Workspace>)
where
V: EventEmitter<ModalEvent> + Render,
{
self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| match e {
ModalEvent::Dismissed => this.modal_layer().hide_modal(cx),
}));
self.open_modal = Some(new_modal.into());
previous_focus_handle: cx.focused(),
focus_handle: cx.focus_handle(),
});
new_modal.update(cx, |modal, cx| modal.focus(cx));
cx.notify();
}
pub fn hide_modal(&mut self, cx: &mut ViewContext<Workspace>) {
self.open_modal.take();
self.subscription.take();
cx.notify();
}
pub fn wrapper_element(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
let mut parent = div().relative().size_full();
for (_, action) in self.registered_modals.iter() {
parent = (action)(parent);
pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_modal) = self.active_modal.take() {
if let Some(previous_focus) = active_modal.previous_focus_handle {
if active_modal.focus_handle.contains_focused(cx) {
previous_focus.focus(cx);
}
}
}
parent.when_some(self.open_modal.as_ref(), |parent, open_modal| {
let container1 = div()
.absolute()
.flex()
.flex_col()
.items_center()
.size_full()
.top_0()
.left_0()
.z_index(400);
// transparent layer
let container2 = v_stack().h(px(0.0)).relative().top_20();
parent.child(container1.child(container2.child(open_modal.clone())))
})
cx.notify();
}
}
impl Render for ModalLayer {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let Some(active_modal) = &self.active_modal else {
return div();
};
div()
.absolute()
.flex()
.flex_col()
.items_center()
.size_full()
.top_0()
.left_0()
.z_index(400)
.child(
v_stack()
.h(px(0.0))
.top_20()
.track_focus(&active_modal.focus_handle)
.on_mouse_down_out(|this: &mut Self, event, cx| {
this.hide_modal(cx);
})
.child(active_modal.modal.clone()),
)
}
}

View File

@ -36,11 +36,12 @@ use futures::{
Future, FutureExt, StreamExt,
};
use gpui::{
actions, div, point, rems, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle,
GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive,
StatelessInteractive, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
WindowBounds, WindowContext, WindowHandle, WindowOptions,
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
@ -434,6 +435,13 @@ pub enum Event {
pub struct Workspace {
weak_self: WeakView<Self>,
focus_handle: FocusHandle,
workspace_actions: Vec<
Box<
dyn Fn(
Div<Workspace, StatefulInteractivity<Workspace>>,
) -> Div<Workspace, StatefulInteractivity<Workspace>>,
>,
>,
zoomed: Option<AnyWeakView>,
zoomed_position: Option<DockPosition>,
center: PaneGroup,
@ -446,7 +454,7 @@ pub struct Workspace {
last_active_center_pane: Option<WeakView<Pane>>,
last_active_view_id: Option<proto::ViewId>,
status_bar: View<StatusBar>,
modal_layer: ModalLayer,
modal_layer: View<ModalLayer>,
// titlebar_item: Option<AnyViewHandle>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>,
@ -598,7 +606,7 @@ impl Workspace {
});
let workspace_handle = cx.view().downgrade();
let modal_layer = ModalLayer::new();
let modal_layer = cx.build_view(|cx| ModalLayer::new());
// todo!()
// cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@ -679,13 +687,10 @@ impl Workspace {
leader_updates_tx,
subscriptions,
pane_history_timestamp,
workspace_actions: Default::default(),
}
}
pub fn modal_layer(&mut self) -> &mut ModalLayer {
&mut self.modal_layer
}
fn new_local(
abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>,
@ -3356,23 +3361,27 @@ impl Workspace {
// #[cfg(any(test, feature = "test-support"))]
// pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
// use node_runtime::FakeNodeRuntime;
#[cfg(any(test, feature = "test-support"))]
pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
use gpui::Context;
use node_runtime::FakeNodeRuntime;
// let client = project.read(cx).client();
// let user_store = project.read(cx).user_store();
let client = project.read(cx).client();
let user_store = project.read(cx).user_store();
// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
// let app_state = Arc::new(AppState {
// languages: project.read(cx).languages().clone(),
// workspace_store,
// client,
// user_store,
// fs: project.read(cx).fs().clone(),
// build_window_options: |_, _, _| Default::default(),
// initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
// node_runtime: FakeNodeRuntime::new(),
// });
// Self::new(0, project, app_state, cx)
// }
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
let app_state = Arc::new(AppState {
languages: project.read(cx).languages().clone(),
workspace_store,
client,
user_store,
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
node_runtime: FakeNodeRuntime::new(),
});
Self::new(0, project, app_state, cx)
}
// fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
// let dock = match position {
@ -3404,6 +3413,35 @@ impl Workspace {
// )
// }
// }
pub fn register_action<A: Action>(
&mut self,
callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
) {
let callback = Arc::new(callback);
self.workspace_actions.push(Box::new(move |div| {
let callback = callback.clone();
div.on_action(move |workspace, event, cx| (callback.clone())(workspace, event, cx))
}));
}
fn add_workspace_actions_listeners(
&self,
mut div: Div<Workspace, StatefulInteractivity<Workspace>>,
) -> Div<Workspace, StatefulInteractivity<Workspace>> {
for action in self.workspace_actions.iter() {
div = (action)(div)
}
div
}
pub fn toggle_modal<V: Modal, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
where
B: FnOnce(&mut ViewContext<V>) -> V,
{
self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
}
}
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
@ -3618,142 +3656,110 @@ impl Render for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.relative()
.size_full()
.flex()
.flex_col()
.font("Zed Sans")
.gap_0()
.justify_start()
.items_start()
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().background)
.child(self.render_titlebar(cx))
.child(Workspace::actions(
// todo! should this be a component a view?
self.modal_layer
.wrapper_element(cx)
.relative()
.flex_1()
.w_full()
.flex()
.overflow_hidden()
.border_t()
.border_b()
.border_color(cx.theme().colors().border)
.child(
div()
.flex()
.flex_row()
.flex_1()
.h_full()
.child(div().flex().flex_1().child(self.left_dock.clone()))
.child(
div()
.flex()
.flex_col()
.flex_1()
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.child(div().flex().flex_1().child(self.bottom_dock.clone())),
)
.child(div().flex().flex_1().child(self.right_dock.clone())),
), // .children(
// Some(
// Panel::new("chat-panel-outer", cx)
// .side(PanelSide::Right)
// .child(ChatPanel::new("chat-panel-inner").messages(vec![
// ChatMessage::new(
// "osiewicz".to_string(),
// "is this thing on?".to_string(),
// DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ChatMessage::new(
// "maxdeviant".to_string(),
// "Reading you loud and clear!".to_string(),
// DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ])),
// )
// .filter(|_| self.is_chat_panel_open()),
// )
// .children(
// Some(
// Panel::new("notifications-panel-outer", cx)
// .side(PanelSide::Right)
// .child(NotificationsPanel::new("notifications-panel-inner")),
// )
// .filter(|_| self.is_notifications_panel_open()),
// )
// .children(
// Some(
// Panel::new("assistant-panel-outer", cx)
// .child(AssistantPanel::new("assistant-panel-inner")),
// )
// .filter(|_| self.is_assistant_panel_open()),
// ),
))
.child(self.status_bar.clone())
// .when(self.debug.show_toast, |this| {
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
// })
// .children(
// Some(
// div()
// .absolute()
// .top(px(50.))
// .left(px(640.))
// .z_index(8)
// .child(LanguageSelector::new("language-selector")),
// )
// .filter(|_| self.is_language_selector_open()),
// )
.z_index(8)
// Debug
.child(
div()
.flex()
.flex_col()
.z_index(9)
.absolute()
.top_20()
.left_1_4()
.w_40()
.gap_2(), // .when(self.show_debug, |this| {
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
// ))
// .child(
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_toast(cx),
// )),
// )
// .child(
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_livestream(cx),
// )),
// )
// })
// .child(
// Button::<Workspace>::new("Toggle Debug")
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
// ),
)
let mut context = DispatchContext::default();
context.insert("Workspace");
cx.with_key_dispatch_context(context, |cx| {
div()
.relative()
.size_full()
.flex()
.flex_col()
.font("Zed Sans")
.gap_0()
.justify_start()
.items_start()
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().background)
.child(self.render_titlebar(cx))
.child(
// todo! should this be a component a view?
self.add_workspace_actions_listeners(div().id("workspace"))
.relative()
.flex_1()
.w_full()
.flex()
.overflow_hidden()
.border_t()
.border_b()
.border_color(cx.theme().colors().border)
.child(self.modal_layer.clone())
.child(
div()
.flex()
.flex_row()
.flex_1()
.h_full()
.child(div().flex().flex_1().child(self.left_dock.clone()))
.child(
div()
.flex()
.flex_col()
.flex_1()
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.child(
div().flex().flex_1().child(self.bottom_dock.clone()),
),
)
.child(div().flex().flex_1().child(self.right_dock.clone())),
),
)
.child(self.status_bar.clone())
// .when(self.debug.show_toast, |this| {
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
// })
// .children(
// Some(
// div()
// .absolute()
// .top(px(50.))
// .left(px(640.))
// .z_index(8)
// .child(LanguageSelector::new("language-selector")),
// )
// .filter(|_| self.is_language_selector_open()),
// )
.z_index(8)
// Debug
.child(
div()
.flex()
.flex_col()
.z_index(9)
.absolute()
.top_20()
.left_1_4()
.w_40()
.gap_2(), // .when(self.show_debug, |this| {
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
// ))
// .child(
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_toast(cx),
// )),
// )
// .child(
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_livestream(cx),
// )),
// )
// })
// .child(
// Button::<Workspace>::new("Toggle Debug")
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
// ),
)
})
}
}
// todo!()
// impl Entity for Workspace {
// type Event = Event;

View File

@ -25,7 +25,7 @@ call = { package = "call2", path = "../call2" }
cli = { path = "../cli" }
# collab_ui = { path = "../collab_ui" }
collections = { path = "../collections" }
# command_palette = { path = "../command_palette" }
command_palette = { package="command_palette2", path = "../command_palette2" }
# component_test = { path = "../component_test" }
# context_menu = { path = "../context_menu" }
client = { package = "client2", path = "../client2" }
@ -74,7 +74,7 @@ util = { path = "../util" }
# vim = { path = "../vim" }
workspace = { package = "workspace2", path = "../workspace2" }
# welcome = { path = "../welcome" }
# zed-actions = {path = "../zed-actions"}
zed_actions = {package = "zed_actions2", path = "../zed_actions2"}
anyhow.workspace = true
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
async-tar = "0.4.2"

View File

@ -142,7 +142,7 @@ fn main() {
// context_menu::init(cx);
project::Project::init(&client, cx);
client::init(&client, cx);
// command_palette::init(cx);
command_palette::init(cx);
language::init(cx);
editor::init(cx);
copilot::init(
@ -761,7 +761,7 @@ fn load_embedded_fonts(cx: &AppContext) {
// #[cfg(not(debug_assertions))]
// async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
// None
// }
//
// #[cfg(not(debug_assertions))]
// fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}

View File

@ -0,0 +1,11 @@
[package]
name = "zed_actions2"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gpui = { package = "gpui2", path = "../gpui2" }
serde.workspace = true

View File

@ -0,0 +1,34 @@
use gpui::{action, actions};
actions!(
About,
DebugElements,
DecreaseBufferFontSize,
Hide,
HideOthers,
IncreaseBufferFontSize,
Minimize,
OpenDefaultKeymap,
OpenDefaultSettings,
OpenKeymap,
OpenLicenses,
OpenLocalSettings,
OpenLog,
OpenSettings,
OpenTelemetryLog,
Quit,
ResetBufferFontSize,
ResetDatabase,
ShowAll,
ToggleFullScreen,
Zoom,
);
#[action]
pub struct OpenBrowser {
pub url: String,
}
#[action]
pub struct OpenZedURL {
pub url: String,
}