diff --git a/Cargo.lock b/Cargo.lock index be36a5cf78..7e56390089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9987,7 +9987,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.10" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index d7d7165e15..cc8264f697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,7 +202,7 @@ tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", re tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"} [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ef6a655bdc..2a8d19f882 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -530,12 +530,17 @@ "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", "enter": "project_panel::Rename", - "space": "project_panel::Open", "backspace": "project_panel::Delete", "alt-cmd-r": "project_panel::RevealInFinder", "alt-shift-f": "project_panel::NewSearchInDirectory" } }, + { + "context": "ProjectPanel && not_editing", + "bindings": { + "space": "project_panel::Open" + } + }, { "context": "CollabPanel && not_editing", "bindings": { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index cac8bf6c54..e472e8c8df 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1218,6 +1218,31 @@ impl View for AssistantPanel { let style = &theme.assistant; if let Some(api_key_editor) = self.api_key_editor.as_ref() { Flex::column() + .with_child( + Text::new( + "To use the assistant panel or inline assistant, you need to add your OpenAI api key.", + style.api_key_prompt.text.clone(), + ), + ) + .with_child( + Text::new( + " - Having a subscription for another service like GitHub Copilot won't work.", + style.api_key_prompt.text.clone(), + ), + ) + .with_child( + Text::new( + " - You can create a api key at: platform.openai.com/api-keys", + style.api_key_prompt.text.clone(), + ), + ) + .with_child( + Text::new( + " ", + style.api_key_prompt.text.clone(), + ) + .aligned(), + ) .with_child( Text::new( "Paste your OpenAI API key and press Enter to use the assistant", @@ -1231,6 +1256,20 @@ impl View for AssistantPanel { .with_style(style.api_key_editor.container) .aligned(), ) + .with_child( + Text::new( + " ", + style.api_key_prompt.text.clone(), + ) + .aligned(), + ) + .with_child( + Text::new( + "Click on the Z button in the status bar to close this panel.", + style.api_key_prompt.text.clone(), + ) + .aligned(), + ) .contained() .with_style(style.api_key_prompt.container) .aligned() diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index b62056a3be..b90df68c2a 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2511,7 +2511,7 @@ impl CollabPanel { } else { el.child( ListHeader::new(text) - .when_some(button, |el, button| el.right_button(button)) + .when_some(button, |el, button| el.meta(button)) .selected(is_selected), ) } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index d76242afa3..f18e4cb2db 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,7 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle2, IconButton, KeyBinding, Tooltip}; +use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle, IconButton, KeyBinding, Tooltip}; use util::ResultExt; use workspace::{notifications::NotifyResultExt, Workspace}; @@ -154,7 +154,7 @@ impl Render for CollabTitlebarItem { .id("project_owner_indicator") .child( Button::new("player", "player") - .style(ButtonStyle2::Subtle) + .style(ButtonStyle::Subtle) .color(Some(Color::Player(0))), ) .tooltip(move |cx| Tooltip::text("Toggle following", cx)), @@ -167,7 +167,7 @@ impl Render for CollabTitlebarItem { .id("titlebar_project_menu_button") .child( Button::new("project_name", "project_name") - .style(ButtonStyle2::Subtle), + .style(ButtonStyle::Subtle), ) .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), ) @@ -179,7 +179,7 @@ impl Render for CollabTitlebarItem { .id("titlebar_git_menu_button") .child( Button::new("branch_name", "branch_name") - .style(ButtonStyle2::Subtle) + .style(ButtonStyle::Subtle) .color(Some(Color::Muted)), ) .tooltip(move |cx| { diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index f9b58b1d56..04688b0549 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -11,7 +11,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; -use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem}; +use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem}; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs index 408142ae07..5aeecbae97 100644 --- a/crates/editor2/src/display_map/wrap_map.rs +++ b/crates/editor2/src/display_map/wrap_map.rs @@ -162,7 +162,7 @@ impl WrapMap { { let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); - let edits = new_snapshot + edits = new_snapshot .update( tab_snapshot, &[TabEdit { diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 65b8b27b6f..9b2681e563 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -63,6 +63,7 @@ use language::{ use lazy_static::lazy_static; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use lsp::{DiagnosticSeverity, LanguageServerId}; +use mouse_context_menu::MouseContextMenu; use movement::TextLayoutDetails; use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ @@ -406,133 +407,17 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) { init_settings(cx); - // cx.register_action_type(Editor::new_file); - // cx.register_action_type(Editor::new_file_in_direction); - // cx.register_action_type(Editor::cancel); - // cx.register_action_type(Editor::newline); - // cx.register_action_type(Editor::newline_above); - // cx.register_action_type(Editor::newline_below); - // cx.register_action_type(Editor::backspace); - // cx.register_action_type(Editor::delete); - // cx.register_action_type(Editor::tab); - // cx.register_action_type(Editor::tab_prev); - // cx.register_action_type(Editor::indent); - // cx.register_action_type(Editor::outdent); - // cx.register_action_type(Editor::delete_line); - // cx.register_action_type(Editor::join_lines); - // cx.register_action_type(Editor::sort_lines_case_sensitive); - // cx.register_action_type(Editor::sort_lines_case_insensitive); - // cx.register_action_type(Editor::reverse_lines); - // cx.register_action_type(Editor::shuffle_lines); - // cx.register_action_type(Editor::convert_to_upper_case); - // cx.register_action_type(Editor::convert_to_lower_case); - // cx.register_action_type(Editor::convert_to_title_case); - // cx.register_action_type(Editor::convert_to_snake_case); - // cx.register_action_type(Editor::convert_to_kebab_case); - // cx.register_action_type(Editor::convert_to_upper_camel_case); - // cx.register_action_type(Editor::convert_to_lower_camel_case); - // cx.register_action_type(Editor::delete_to_previous_word_start); - // cx.register_action_type(Editor::delete_to_previous_subword_start); - // cx.register_action_type(Editor::delete_to_next_word_end); - // cx.register_action_type(Editor::delete_to_next_subword_end); - // cx.register_action_type(Editor::delete_to_beginning_of_line); - // cx.register_action_type(Editor::delete_to_end_of_line); - // cx.register_action_type(Editor::cut_to_end_of_line); - // cx.register_action_type(Editor::duplicate_line); - // cx.register_action_type(Editor::move_line_up); - // cx.register_action_type(Editor::move_line_down); - // cx.register_action_type(Editor::transpose); - // cx.register_action_type(Editor::cut); - // cx.register_action_type(Editor::copy); - // cx.register_action_type(Editor::paste); - // cx.register_action_type(Editor::undo); - // cx.register_action_type(Editor::redo); - // cx.register_action_type(Editor::move_page_up); - // cx.register_action_type::(); - // cx.register_action_type(Editor::move_page_down); - // cx.register_action_type(Editor::next_screen); - // cx.register_action_type::(); - // cx.register_action_type::(); - // cx.register_action_type(Editor::move_to_previous_word_start); - // cx.register_action_type(Editor::move_to_previous_subword_start); - // cx.register_action_type(Editor::move_to_next_word_end); - // cx.register_action_type(Editor::move_to_next_subword_end); - // cx.register_action_type(Editor::move_to_beginning_of_line); - // cx.register_action_type(Editor::move_to_end_of_line); - // cx.register_action_type(Editor::move_to_start_of_paragraph); - // cx.register_action_type(Editor::move_to_end_of_paragraph); - // cx.register_action_type(Editor::move_to_beginning); - // cx.register_action_type(Editor::move_to_end); - // cx.register_action_type(Editor::select_up); - // cx.register_action_type(Editor::select_down); - // cx.register_action_type(Editor::select_left); - // cx.register_action_type(Editor::select_right); - // cx.register_action_type(Editor::select_to_previous_word_start); - // cx.register_action_type(Editor::select_to_previous_subword_start); - // cx.register_action_type(Editor::select_to_next_word_end); - // cx.register_action_type(Editor::select_to_next_subword_end); - // cx.register_action_type(Editor::select_to_beginning_of_line); - // cx.register_action_type(Editor::select_to_end_of_line); - // cx.register_action_type(Editor::select_to_start_of_paragraph); - // cx.register_action_type(Editor::select_to_end_of_paragraph); - // cx.register_action_type(Editor::select_to_beginning); - // cx.register_action_type(Editor::select_to_end); - // cx.register_action_type(Editor::select_all); - // cx.register_action_type(Editor::select_all_matches); - // cx.register_action_type(Editor::select_line); - // cx.register_action_type(Editor::split_selection_into_lines); - // cx.register_action_type(Editor::add_selection_above); - // cx.register_action_type(Editor::add_selection_below); - // cx.register_action_type(Editor::select_next); - // cx.register_action_type(Editor::select_previous); - // cx.register_action_type(Editor::toggle_comments); - // cx.register_action_type(Editor::select_larger_syntax_node); - // cx.register_action_type(Editor::select_smaller_syntax_node); - // cx.register_action_type(Editor::move_to_enclosing_bracket); - // cx.register_action_type(Editor::undo_selection); - // cx.register_action_type(Editor::redo_selection); - // cx.register_action_type(Editor::go_to_diagnostic); - // cx.register_action_type(Editor::go_to_prev_diagnostic); - // cx.register_action_type(Editor::go_to_hunk); - // cx.register_action_type(Editor::go_to_prev_hunk); - // cx.register_action_type(Editor::go_to_definition); - // cx.register_action_type(Editor::go_to_definition_split); - // cx.register_action_type(Editor::go_to_type_definition); - // cx.register_action_type(Editor::go_to_type_definition_split); - // cx.register_action_type(Editor::fold); - // cx.register_action_type(Editor::fold_at); - // cx.register_action_type(Editor::unfold_lines); - // cx.register_action_type(Editor::unfold_at); - // cx.register_action_type(Editor::gutter_hover); - // cx.register_action_type(Editor::fold_selected_ranges); - // cx.register_action_type(Editor::show_completions); - // cx.register_action_type(Editor::toggle_code_actions); - // cx.register_action_type(Editor::open_excerpts); - // cx.register_action_type(Editor::toggle_soft_wrap); - // cx.register_action_type(Editor::toggle_inlay_hints); - // cx.register_action_type(Editor::reveal_in_finder); - // cx.register_action_type(Editor::copy_path); - // cx.register_action_type(Editor::copy_relative_path); - // cx.register_action_type(Editor::copy_highlight_json); - // cx.add_async_action(Editor::format); - // cx.register_action_type(Editor::restart_language_server); - // cx.register_action_type(Editor::show_character_palette); - // cx.add_async_action(Editor::confirm_completion); - // cx.add_async_action(Editor::confirm_code_action); - // cx.add_async_action(Editor::rename); - // cx.add_async_action(Editor::confirm_rename); - // cx.add_async_action(Editor::find_all_references); - // cx.register_action_type(Editor::next_copilot_suggestion); - // cx.register_action_type(Editor::previous_copilot_suggestion); - // cx.register_action_type(Editor::copilot_suggest); - // cx.register_action_type(Editor::context_menu_first); - // cx.register_action_type(Editor::context_menu_prev); - // cx.register_action_type(Editor::context_menu_next); - // cx.register_action_type(Editor::context_menu_last); workspace::register_project_item::(cx); workspace::register_followable_item::(cx); workspace::register_deserializable_item::(cx); + cx.observe_new_views( + |workspace: &mut Workspace, cx: &mut ViewContext| { + workspace.register_action(Editor::new_file); + workspace.register_action(Editor::new_file_in_direction); + }, + ) + .detach(); } trait InvalidationRegion { @@ -620,8 +505,6 @@ pub struct Editor { ime_transaction: Option, active_diagnostics: Option, soft_wrap_mode_override: Option, - // get_field_editor_theme: Option>, - // override_text_style: Option>, project: Option>, collaboration_hub: Option>, blink_manager: Model, @@ -635,7 +518,7 @@ pub struct Editor { inlay_background_highlights: TreeMap, InlayBackgroundHighlight>, nav_history: Option, context_menu: RwLock>, - // mouse_context_menu: View, + mouse_context_menu: Option, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, available_code_actions: Option<(Model, Arc<[CodeAction]>)>, @@ -1729,21 +1612,11 @@ impl Editor { // Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) // } - // pub fn auto_height( - // max_lines: usize, - // field_editor_style: Option>, - // cx: &mut ViewContext, - // ) -> Self { - // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); - // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); - // Self::new( - // EditorMode::AutoHeight { max_lines }, - // buffer, - // None, - // field_editor_style, - // cx, - // ) - // } + pub fn auto_height(max_lines: usize, cx: &mut ViewContext) -> Self { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new())); + let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx) + } pub fn for_buffer( buffer: Model, @@ -1763,14 +1636,7 @@ impl Editor { } pub fn clone(&self, cx: &mut ViewContext) -> Self { - let mut clone = Self::new( - self.mode, - self.buffer.clone(), - self.project.clone(), - // todo! - // self.get_field_editor_theme.clone(), - cx, - ); + let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx); self.display_map.update(cx, |display_map, cx| { let snapshot = display_map.snapshot(cx); clone.display_map.update(cx, |display_map, cx| { @@ -1787,17 +1653,11 @@ impl Editor { mode: EditorMode, buffer: Model, project: Option>, - // todo!() - // get_field_editor_theme: Option>, cx: &mut ViewContext, ) -> Self { - // let editor_view_id = cx.view_id(); let style = cx.text_style(); let font_size = style.font_size.to_pixels(cx.rem_size()); let display_map = cx.build_model(|cx| { - // todo!() - // let settings = settings::get::(cx); - // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx) }); @@ -1853,7 +1713,6 @@ impl Editor { ime_transaction: Default::default(), active_diagnostics: None, soft_wrap_mode_override, - // get_field_editor_theme, collaboration_hub: project.clone().map(|project| Box::new(project) as _), project, blink_manager: blink_manager.clone(), @@ -1867,8 +1726,7 @@ impl Editor { inlay_background_highlights: Default::default(), nav_history: None, context_menu: RwLock::new(None), - // mouse_context_menu: cx - // .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), + mouse_context_menu: None, completion_tasks: Default::default(), next_completion_id: 0, next_inlay_id: 0, @@ -1877,7 +1735,6 @@ impl Editor { document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, - // override_text_style: None, cursor_shape: Default::default(), autoindent_mode: Some(AutoindentMode::EachLine), collapse_matches: false, @@ -1995,25 +1852,25 @@ impl Editor { } } - // pub fn new_file_in_direction( - // workspace: &mut Workspace, - // action: &workspace::NewFileInDirection, - // cx: &mut ViewContext, - // ) { - // let project = workspace.project().clone(); - // if project.read(cx).is_remote() { - // cx.propagate(); - // } else if let Some(buffer) = project - // .update(cx, |project, cx| project.create_buffer("", None, cx)) - // .log_err() - // { - // workspace.split_item( - // action.0, - // Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))), - // cx, - // ); - // } - // } + pub fn new_file_in_direction( + workspace: &mut Workspace, + action: &workspace::NewFileInDirection, + cx: &mut ViewContext, + ) { + let project = workspace.project().clone(); + if project.read(cx).is_remote() { + cx.propagate(); + } else if let Some(buffer) = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .log_err() + { + workspace.split_item( + action.0, + Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))), + cx, + ); + } + } pub fn replica_id(&self, cx: &AppContext) -> ReplicaId { self.buffer.read(cx).replica_id() @@ -8369,6 +8226,18 @@ impl Editor { cx.notify(); } + pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext) { + let rem_size = cx.rem_size(); + self.display_map.update(cx, |map, cx| { + map.set_font( + style.text.font(), + style.text.font_size.to_pixels(rem_size), + cx, + ) + }); + self.style = Some(style); + } + pub fn set_wrap_width(&self, width: Option, cx: &mut AppContext) -> bool { self.display_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) @@ -8791,62 +8660,56 @@ impl Editor { // self.searchable // } - // fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext) { - // let active_item = workspace.active_item(cx); - // let editor_handle = if let Some(editor) = active_item - // .as_ref() - // .and_then(|item| item.act_as::(cx)) - // { - // editor - // } else { - // cx.propagate(); - // return; - // }; + fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { + let buffer = self.buffer.read(cx); + if buffer.is_singleton() { + cx.propagate(); + return; + } - // let editor = editor_handle.read(cx); - // let buffer = editor.buffer.read(cx); - // if buffer.is_singleton() { - // cx.propagate(); - // return; - // } + let Some(workspace) = self.workspace() else { + cx.propagate(); + return; + }; - // let mut new_selections_by_buffer = HashMap::default(); - // for selection in editor.selections.all::(cx) { - // for (buffer, mut range, _) in - // buffer.range_to_buffer_ranges(selection.start..selection.end, cx) - // { - // if selection.reversed { - // mem::swap(&mut range.start, &mut range.end); - // } - // new_selections_by_buffer - // .entry(buffer) - // .or_insert(Vec::new()) - // .push(range) - // } - // } + let mut new_selections_by_buffer = HashMap::default(); + for selection in self.selections.all::(cx) { + for (buffer, mut range, _) in + buffer.range_to_buffer_ranges(selection.start..selection.end, cx) + { + if selection.reversed { + mem::swap(&mut range.start, &mut range.end); + } + new_selections_by_buffer + .entry(buffer) + .or_insert(Vec::new()) + .push(range) + } + } - // editor_handle.update(cx, |editor, cx| { - // editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx); - // }); - // let pane = workspace.active_pane().clone(); - // pane.update(cx, |pane, _| pane.disable_history()); + self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx); - // // We defer the pane interaction because we ourselves are a workspace item - // // and activating a new item causes the pane to call a method on us reentrantly, - // // which panics if we're on the stack. - // cx.defer(move |workspace, cx| { - // for (buffer, ranges) in new_selections_by_buffer.into_iter() { - // let editor = workspace.open_project_item::(buffer, cx); - // editor.update(cx, |editor, cx| { - // editor.change_selections(Some(Autoscroll::newest()), cx, |s| { - // s.select_ranges(ranges); - // }); - // }); - // } + // We defer the pane interaction because we ourselves are a workspace item + // and activating a new item causes the pane to call a method on us reentrantly, + // which panics if we're on the stack. + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, _| pane.disable_history()); - // pane.update(cx, |pane, _| pane.enable_history()); - // }); - // } + for (buffer, ranges) in new_selections_by_buffer.into_iter() { + let editor = workspace.open_project_item::(buffer, cx); + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::newest()), cx, |s| { + s.select_ranges(ranges); + }); + }); + } + + pane.update(cx, |pane, _| pane.enable_history()); + }) + }); + } fn jump( &mut self, @@ -9392,7 +9255,7 @@ impl Render for Editor { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let settings = ThemeSettings::get_global(cx); let text_style = match self.mode { - EditorMode::SingleLine => TextStyle { + EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle { color: cx.theme().colors().text, font_family: settings.ui_font.family.clone(), font_features: settings.ui_font.features, @@ -9405,8 +9268,6 @@ impl Render for Editor { white_space: WhiteSpace::Normal, }, - EditorMode::AutoHeight { max_lines } => todo!(), - EditorMode::Full => TextStyle { color: cx.theme().colors().text, font_family: settings.buffer_font.family.clone(), @@ -9441,106 +9302,6 @@ impl Render for Editor { } } -// impl View for Editor { -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let style = self.style(cx); -// let font_changed = self.display_map.update(cx, |map, cx| { -// map.set_fold_ellipses_color(style.folds.ellipses.text_color); -// map.set_font_with_size(style.text.font_id, style.text.font_size, cx) -// }); - -// if font_changed { -// cx.defer(move |editor, cx: &mut ViewContext| { -// hide_hover(editor, cx); -// hide_link_definition(editor, cx); -// }); -// } - -// Stack::new() -// .with_child(EditorElement::new(style.clone())) -// .with_child(ChildView::new(&self.mouse_context_menu, cx)) -// .into_any() -// } - -// fn ui_name() -> &'static str { -// "Editor" -// } - -// fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext) { -// if cx.is_self_focused() { -// let focused_event = EditorFocused(cx.handle()); -// cx.emit(Event::Focused); -// cx.emit_global(focused_event); -// } -// if let Some(rename) = self.pending_rename.as_ref() { -// cx.focus(&rename.editor); -// } else if cx.is_self_focused() || !focused.is::() { -// if !self.focused { -// self.blink_manager.update(cx, BlinkManager::enable); -// } -// self.focused = true; -// self.buffer.update(cx, |buffer, cx| { -// buffer.finalize_last_transaction(cx); -// if self.leader_peer_id.is_none() { -// buffer.set_active_selections( -// &self.selections.disjoint_anchors(), -// self.selections.line_mode, -// self.cursor_shape, -// cx, -// ); -// } -// }); -// } -// } - -// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext) { -// let blurred_event = EditorBlurred(cx.handle()); -// cx.emit_global(blurred_event); -// self.focused = false; -// self.blink_manager.update(cx, BlinkManager::disable); -// self.buffer -// .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); -// self.hide_context_menu(cx); -// hide_hover(self, cx); -// cx.emit(Event::Blurred); -// cx.notify(); -// } - -// fn modifiers_changed( -// &mut self, -// event: &gpui::platform::ModifiersChangedEvent, -// cx: &mut ViewContext, -// ) -> bool { -// let pending_selection = self.has_pending_selection(); - -// if let Some(point) = &self.link_go_to_definition_state.last_trigger_point { -// if event.cmd && !pending_selection { -// let point = point.clone(); -// let snapshot = self.snapshot(cx); -// let kind = point.definition_kind(event.shift); - -// show_link_definition(kind, self, point, snapshot, cx); -// return false; -// } -// } - -// { -// if self.link_go_to_definition_state.symbol_range.is_some() -// || !self.link_go_to_definition_state.definitions.is_empty() -// { -// self.link_go_to_definition_state.symbol_range.take(); -// self.link_go_to_definition_state.definitions.clear(); -// cx.notify(); -// } - -// self.link_go_to_definition_state.task = None; - -// self.clear_highlights::(cx); -// } - -// false -// } - impl InputHandler for Editor { fn text_for_range( &mut self, @@ -9787,72 +9548,6 @@ impl InputHandler for Editor { } } -// fn build_style( -// settings: &ThemeSettings, -// get_field_editor_theme: Option<&GetFieldEditorTheme>, -// override_text_style: Option<&OverrideTextStyle>, -// cx: &mut AppContext, -// ) -> EditorStyle { -// let font_cache = cx.font_cache(); -// let line_height_scalar = settings.line_height(); -// let theme_id = settings.theme.meta.id; -// let mut theme = settings.theme.editor.clone(); -// let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { -// let field_editor_theme = get_field_editor_theme(&settings.theme); -// theme.text_color = field_editor_theme.text.color; -// theme.selection = field_editor_theme.selection; -// theme.background = field_editor_theme -// .container -// .background_color -// .unwrap_or_default(); -// EditorStyle { -// text: field_editor_theme.text, -// placeholder_text: field_editor_theme.placeholder_text, -// line_height_scalar, -// theme, -// theme_id, -// } -// } else { -// todo!(); -// // let font_family_id = settings.buffer_font_family; -// // let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); -// // let font_properties = Default::default(); -// // let font_id = font_cache -// // .select_font(font_family_id, &font_properties) -// // .unwrap(); -// // let font_size = settings.buffer_font_size(cx); -// // EditorStyle { -// // text: TextStyle { -// // color: settings.theme.editor.text_color, -// // font_family_name, -// // font_family_id, -// // font_id, -// // font_size, -// // font_properties, -// // underline: Default::default(), -// // soft_wrap: false, -// // }, -// // placeholder_text: None, -// // line_height_scalar, -// // theme, -// // theme_id, -// // } -// }; - -// if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) { -// if let Some(highlighted) = style -// .text -// .clone() -// .highlight(highlight_style, font_cache) -// .log_err() -// { -// style.text = highlighted; -// } -// } - -// style -// } - trait SelectionExt { fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range; fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range; diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 24402c7e37..3abe5a37f9 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -9,9 +9,11 @@ use crate::{ self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, link_go_to_definition::{ - go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, - update_inlay_link_and_hover_points, GoToDefinitionTrigger, + go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition, + update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger, + LinkGoToDefinitionState, }, + mouse_context_menu, scroll::scroll_amount::ScrollAmount, CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point, @@ -19,14 +21,15 @@ use crate::{ }; use anyhow::Result; use collections::{BTreeMap, HashMap}; +use git::diff::DiffHunkStatus; use gpui::{ - div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, - BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, - ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement, - IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size, - StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, - ViewContext, WeakView, WindowContext, WrappedLine, + div, overlay, point, px, relative, size, transparent_black, Action, AnchorCorner, AnyElement, + AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle, + DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, EntityId, Hsla, + InteractiveBounds, InteractiveElement, IntoElement, LineLayout, ModifiersChangedEvent, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce, + ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -139,8 +142,6 @@ impl EditorElement { register_action(view, cx, Editor::move_right); register_action(view, cx, Editor::move_down); register_action(view, cx, Editor::move_up); - // on_action(cx, Editor::new_file); todo!() - // on_action(cx, Editor::new_file_in_direction); todo!() register_action(view, cx, Editor::cancel); register_action(view, cx, Editor::newline); register_action(view, cx, Editor::newline_above); @@ -263,7 +264,7 @@ impl EditorElement { register_action(view, cx, Editor::fold_selected_ranges); register_action(view, cx, Editor::show_completions); register_action(view, cx, Editor::toggle_code_actions); - // on_action(cx, Editor::open_excerpts); todo!() + register_action(view, cx, Editor::open_excerpts); register_action(view, cx, Editor::toggle_soft_wrap); register_action(view, cx, Editor::toggle_inlay_hints); register_action(view, cx, hover_popover::hover); @@ -312,7 +313,57 @@ impl EditorElement { register_action(view, cx, Editor::context_menu_last); } - fn mouse_down( + fn register_key_listeners(&self, cx: &mut WindowContext) { + cx.on_key_event({ + let editor = self.editor.clone(); + move |event: &ModifiersChangedEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) { + cx.stop_propagation(); + } + } + }); + } + + fn modifiers_changed( + editor: &mut Editor, + event: &ModifiersChangedEvent, + cx: &mut ViewContext, + ) -> bool { + let pending_selection = editor.has_pending_selection(); + + if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point { + if event.command && !pending_selection { + let point = point.clone(); + let snapshot = editor.snapshot(cx); + let kind = point.definition_kind(event.shift); + + show_link_definition(kind, editor, point, snapshot, cx); + return false; + } + } + + { + if editor.link_go_to_definition_state.symbol_range.is_some() + || !editor.link_go_to_definition_state.definitions.is_empty() + { + editor.link_go_to_definition_state.symbol_range.take(); + editor.link_go_to_definition_state.definitions.clear(); + cx.notify(); + } + + editor.link_go_to_definition_state.task = None; + + editor.clear_highlights::(cx); + } + + false + } + + fn mouse_left_down( editor: &mut Editor, event: &MouseDownEvent, position_map: &PositionMap, @@ -365,25 +416,25 @@ impl EditorElement { true } - // fn mouse_right_down( - // editor: &mut Editor, - // position: gpui::Point, - // position_map: &PositionMap, - // text_bounds: Bounds, - // cx: &mut EventContext, - // ) -> bool { - // if !text_bounds.contains_point(position) { - // return false; - // } - // let point_for_position = position_map.point_for_position(text_bounds, position); - // mouse_context_menu::deploy_context_menu( - // editor, - // position, - // point_for_position.previous_valid, - // cx, - // ); - // true - // } + fn mouse_right_down( + editor: &mut Editor, + event: &MouseDownEvent, + position_map: &PositionMap, + text_bounds: Bounds, + cx: &mut ViewContext, + ) -> bool { + if !text_bounds.contains_point(&event.position) { + return false; + } + let point_for_position = position_map.point_for_position(text_bounds, event.position); + mouse_context_menu::deploy_context_menu( + editor, + event.position, + point_for_position.previous_valid, + cx, + ); + true + } fn mouse_up( editor: &mut Editor, @@ -725,87 +776,85 @@ impl EditorElement { } fn paint_diff_hunks(bounds: Bounds, layout: &LayoutState, cx: &mut WindowContext) { - // todo!() - // let diff_style = &theme::current(cx).editor.diff.clone(); - // let line_height = layout.position_map.line_height; + let line_height = layout.position_map.line_height; - // let scroll_position = layout.position_map.snapshot.scroll_position(); - // let scroll_top = scroll_position.y * line_height; + let scroll_position = layout.position_map.snapshot.scroll_position(); + let scroll_top = scroll_position.y * line_height; - // for hunk in &layout.display_hunks { - // let (display_row_range, status) = match hunk { - // //TODO: This rendering is entirely a horrible hack - // &DisplayDiffHunk::Folded { display_row: row } => { - // let start_y = row as f32 * line_height - scroll_top; - // let end_y = start_y + line_height; + for hunk in &layout.display_hunks { + let (display_row_range, status) = match hunk { + //TODO: This rendering is entirely a horrible hack + &DisplayDiffHunk::Folded { display_row: row } => { + let start_y = row as f32 * line_height - scroll_top; + let end_y = start_y + line_height; - // let width = diff_style.removed_width_em * line_height; - // let highlight_origin = bounds.origin + point(-width, start_y); - // let highlight_size = point(width * 2., end_y - start_y); - // let highlight_bounds = Bounds::::new(highlight_origin, highlight_size); + let width = 0.275 * line_height; + let highlight_origin = bounds.origin + point(-width, start_y); + let highlight_size = size(width * 2., end_y - start_y); + let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + cx.paint_quad( + highlight_bounds, + Corners::all(1. * line_height), + gpui::yellow(), // todo!("use the right color") + Edges::default(), + transparent_black(), + ); - // cx.paint_quad(Quad { - // bounds: highlight_bounds, - // background: Some(diff_style.modified), - // border: Border::new(0., Color::transparent_black()).into(), - // corner_radii: (1. * line_height).into(), - // }); + continue; + } - // continue; - // } + DisplayDiffHunk::Unfolded { + display_row_range, + status, + } => (display_row_range, status), + }; - // DisplayDiffHunk::Unfolded { - // display_row_range, - // status, - // } => (display_row_range, status), - // }; + let color = match status { + DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color") + DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color") - // let color = match status { - // DiffHunkStatus::Added => diff_style.inserted, - // DiffHunkStatus::Modified => diff_style.modified, + //TODO: This rendering is entirely a horrible hack + DiffHunkStatus::Removed => { + let row = display_row_range.start; - // //TODO: This rendering is entirely a horrible hack - // DiffHunkStatus::Removed => { - // let row = display_row_range.start; + let offset = line_height / 2.; + let start_y = row as f32 * line_height - offset - scroll_top; + let end_y = start_y + line_height; - // let offset = line_height / 2.; - // let start_y = row as f32 * line_height - offset - scroll_top; - // let end_y = start_y + line_height; + let width = 0.275 * line_height; + let highlight_origin = bounds.origin + point(-width, start_y); + let highlight_size = size(width * 2., end_y - start_y); + let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + cx.paint_quad( + highlight_bounds, + Corners::all(1. * line_height), + gpui::red(), // todo!("use the right color") + Edges::default(), + transparent_black(), + ); - // let width = diff_style.removed_width_em * line_height; - // let highlight_origin = bounds.origin + point(-width, start_y); - // let highlight_size = point(width * 2., end_y - start_y); - // let highlight_bounds = Bounds::::new(highlight_origin, highlight_size); + continue; + } + }; - // cx.paint_quad(Quad { - // bounds: highlight_bounds, - // background: Some(diff_style.deleted), - // border: Border::new(0., Color::transparent_black()).into(), - // corner_radii: (1. * line_height).into(), - // }); + let start_row = display_row_range.start; + let end_row = display_row_range.end; - // continue; - // } - // }; + let start_y = start_row as f32 * line_height - scroll_top; + let end_y = end_row as f32 * line_height - scroll_top; - // let start_row = display_row_range.start; - // let end_row = display_row_range.end; - - // let start_y = start_row as f32 * line_height - scroll_top; - // let end_y = end_row as f32 * line_height - scroll_top; - - // let width = diff_style.width_em * line_height; - // let highlight_origin = bounds.origin + point(-width, start_y); - // let highlight_size = point(width * 2., end_y - start_y); - // let highlight_bounds = Bounds::::new(highlight_origin, highlight_size); - - // cx.paint_quad(Quad { - // bounds: highlight_bounds, - // background: Some(color), - // border: Border::new(0., Color::transparent_black()).into(), - // corner_radii: (diff_style.corner_radius * line_height).into(), - // }); - // } + let width = 0.275 * line_height; + let highlight_origin = bounds.origin + point(-width, start_y); + let highlight_size = size(width * 2., end_y - start_y); + let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + cx.paint_quad( + highlight_bounds, + Corners::all(0.05 * line_height), + color, // todo!("use the right color") + Edges::default(), + transparent_black(), + ); + } } fn paint_text( @@ -831,15 +880,19 @@ impl EditorElement { bounds: text_bounds, }), |cx| { - // todo!("cursor region") - // cx.scene().push_cursor_region(CursorRegion { - // bounds, - // style: if !editor.link_go_to_definition_state.definitions.is_empty { - // CursorStyle::PointingHand - // } else { - // CursorStyle::IBeam - // }, - // }); + if text_bounds.contains_point(&cx.mouse_position()) { + if self + .editor + .read(cx) + .link_go_to_definition_state + .definitions + .is_empty() + { + cx.set_cursor_style(CursorStyle::IBeam); + } else { + cx.set_cursor_style(CursorStyle::PointingHand); + } + } let fold_corner_radius = 0.15 * layout.position_map.line_height; cx.with_element_id(Some("folds"), |cx| { @@ -1138,6 +1191,22 @@ impl EditorElement { } } } + + if let Some(mouse_context_menu) = + self.editor.read(cx).mouse_context_menu.as_ref() + { + let element = overlay() + .position(mouse_context_menu.position) + .child(mouse_context_menu.context_menu.clone()) + .anchor(AnchorCorner::TopLeft) + .snap_to_window(); + element.draw( + gpui::Point::default(), + size(AvailableSpace::MinContent, AvailableSpace::MinContent), + cx, + |_, _| {}, + ); + } }) }, ) @@ -1662,11 +1731,6 @@ impl EditorElement { cx: &mut WindowContext, ) -> LayoutState { self.editor.update(cx, |editor, cx| { - // let mut size = constraint.max; - // if size.x.is_infinite() { - // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); - // } - let snapshot = editor.snapshot(cx); let style = self.style.clone(); @@ -1702,6 +1766,7 @@ impl EditorElement { }; editor.gutter_width = gutter_width; + let text_width = bounds.size.width - gutter_width; let overscroll = size(em_width, px(0.)); let snapshot = { @@ -1728,25 +1793,6 @@ impl EditorElement { .collect::>(); let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; - // todo!("this should happen during layout") - let editor_mode = snapshot.mode; - if let EditorMode::AutoHeight { max_lines } = editor_mode { - todo!() - // size.set_y( - // scroll_height - // .min(constraint.max_along(Axis::Vertical)) - // .max(constraint.min_along(Axis::Vertical)) - // .max(line_height) - // .min(line_height * max_lines as f32), - // ) - } else if let EditorMode::SingleLine = editor_mode { - bounds.size.height = line_height.min(bounds.size.height); - } - // todo!() - // else if size.y.is_infinite() { - // // size.set_y(scroll_height); - // } - // let gutter_size = size(gutter_width, bounds.size.height); let text_size = size(text_width, bounds.size.height); @@ -2064,7 +2110,7 @@ impl EditorElement { .unwrap(); LayoutState { - mode: editor_mode, + mode: snapshot.mode, position_map: Arc::new(PositionMap { size: bounds.size, scroll_position: point( @@ -2308,10 +2354,10 @@ impl EditorElement { return; } - let should_cancel = editor.update(cx, |editor, cx| { + let handled = editor.update(cx, |editor, cx| { Self::scroll(editor, event, &position_map, &interactive_bounds, cx) }); - if should_cancel { + if handled { cx.stop_propagation(); } } @@ -2327,19 +2373,25 @@ impl EditorElement { return; } - let should_cancel = editor.update(cx, |editor, cx| { - Self::mouse_down( - editor, - event, - &position_map, - text_bounds, - gutter_bounds, - &stacking_order, - cx, - ) - }); + let handled = match event.button { + MouseButton::Left => editor.update(cx, |editor, cx| { + Self::mouse_left_down( + editor, + event, + &position_map, + text_bounds, + gutter_bounds, + &stacking_order, + cx, + ) + }), + MouseButton::Right => editor.update(cx, |editor, cx| { + Self::mouse_right_down(editor, event, &position_map, text_bounds, cx) + }), + _ => false, + }; - if should_cancel { + if handled { cx.stop_propagation() } } @@ -2351,7 +2403,7 @@ impl EditorElement { let stacking_order = cx.stacking_order().clone(); move |event: &MouseUpEvent, phase, cx| { - let should_cancel = editor.update(cx, |editor, cx| { + let handled = editor.update(cx, |editor, cx| { Self::mouse_up( editor, event, @@ -2362,26 +2414,11 @@ impl EditorElement { ) }); - if should_cancel { + if handled { cx.stop_propagation() } } }); - //todo!() - // on_down(MouseButton::Right, { - // let position_map = layout.position_map.clone(); - // move |event, editor, cx| { - // if !Self::mouse_right_down( - // editor, - // event.position, - // position_map.as_ref(), - // text_bounds, - // cx, - // ) { - // cx.propagate_event(); - // } - // } - // }); cx.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); @@ -2617,19 +2654,44 @@ impl Element for EditorElement { cx: &mut gpui::WindowContext, ) -> (gpui::LayoutId, Self::State) { self.editor.update(cx, |editor, cx| { - editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. + editor.set_style(self.style.clone(), cx); - let rem_size = cx.rem_size(); - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = match editor.mode { + let layout_id = match editor.mode { EditorMode::SingleLine => { - self.style.text.line_height_in_pixels(cx.rem_size()).into() + let rem_size = cx.rem_size(); + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); + cx.request_layout(&style, None) + } + EditorMode::AutoHeight { max_lines } => { + let editor_handle = cx.view().clone(); + let max_line_number_width = + self.max_line_number_width(&editor.snapshot(cx), cx); + cx.request_measured_layout( + Style::default(), + move |known_dimensions, available_space, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + } + EditorMode::Full => { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) } - EditorMode::AutoHeight { .. } => todo!(), - EditorMode::Full => relative(1.).into(), }; - let layout_id = cx.request_layout(&style, None); (layout_id, ()) }) @@ -2657,6 +2719,7 @@ impl Element for EditorElement { let dispatch_context = self.editor.read(cx).dispatch_context(cx); cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| { self.register_actions(cx); + self.register_key_listeners(cx); // We call with_z_index to establish a new stacking context. cx.with_z_index(0, |cx| { @@ -2698,604 +2761,6 @@ impl IntoElement for EditorElement { } } -// impl EditorElement { -// type LayoutState = LayoutState; -// type PaintState = (); - -// fn layout( -// &mut self, -// constraint: SizeConstraint, -// editor: &mut Editor, -// cx: &mut ViewContext, -// ) -> (gpui::Point, Self::LayoutState) { -// let mut size = constraint.max; -// if size.x.is_infinite() { -// unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); -// } - -// let snapshot = editor.snapshot(cx); -// let style = self.style.clone(); - -// let line_height = (style.text.font_size * style.line_height_scalar).round(); - -// let gutter_padding; -// let gutter_width; -// let gutter_margin; -// if snapshot.show_gutter { -// let em_width = style.text.em_width(cx.font_cache()); -// gutter_padding = (em_width * style.gutter_padding_factor).round(); -// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; -// gutter_margin = -style.text.descent(cx.font_cache()); -// } else { -// gutter_padding = 0.0; -// gutter_width = 0.0; -// gutter_margin = 0.0; -// }; - -// let text_width = size.x - gutter_width; -// let em_width = style.text.em_width(cx.font_cache()); -// let em_advance = style.text.em_advance(cx.font_cache()); -// let overscroll = point(em_width, 0.); -// let snapshot = { -// editor.set_visible_line_count(size.y / line_height, cx); - -// let editor_width = text_width - gutter_margin - overscroll.x - em_width; -// let wrap_width = match editor.soft_wrap_mode(cx) { -// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, -// SoftWrap::EditorWidth => editor_width, -// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), -// }; - -// if editor.set_wrap_width(Some(wrap_width), cx) { -// editor.snapshot(cx) -// } else { -// snapshot -// } -// }; - -// let wrap_guides = editor -// .wrap_guides(cx) -// .iter() -// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) -// .collect(); - -// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height; -// if let EditorMode::AutoHeight { max_lines } = snapshot.mode { -// size.set_y( -// scroll_height -// .min(constraint.max_along(Axis::Vertical)) -// .max(constraint.min_along(Axis::Vertical)) -// .max(line_height) -// .min(line_height * max_lines as f32), -// ) -// } else if let EditorMode::SingleLine = snapshot.mode { -// size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) -// } else if size.y.is_infinite() { -// size.set_y(scroll_height); -// } -// let gutter_size = point(gutter_width, size.y); -// let text_size = point(text_width, size.y); - -// let autoscroll_horizontally = editor.autoscroll_vertically(size.y, line_height, cx); -// let mut snapshot = editor.snapshot(cx); - -// let scroll_position = snapshot.scroll_position(); -// // The scroll position is a fractional point, the whole number of which represents -// // the top of the window in terms of display rows. -// let start_row = scroll_position.y as u32; -// let height_in_lines = size.y / line_height; -// let max_row = snapshot.max_point().row(); - -// // Add 1 to ensure selections bleed off screen -// let end_row = 1 + cmp::min( -// (scroll_position.y + height_in_lines).ceil() as u32, -// max_row, -// ); - -// let start_anchor = if start_row == 0 { -// Anchor::min() -// } else { -// snapshot -// .buffer_snapshot -// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) -// }; -// let end_anchor = if end_row > max_row { -// Anchor::max -// } else { -// snapshot -// .buffer_snapshot -// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) -// }; - -// let mut selections: Vec<(SelectionStyle, Vec)> = Vec::new(); -// let mut active_rows = BTreeMap::new(); -// let mut fold_ranges = Vec::new(); -// let is_singleton = editor.is_singleton(cx); - -// let highlighted_rows = editor.highlighted_rows(); -// let theme = theme::current(cx); -// let highlighted_ranges = editor.background_highlights_in_range( -// start_anchor..end_anchor, -// &snapshot.display_snapshot, -// theme.as_ref(), -// ); - -// fold_ranges.extend( -// snapshot -// .folds_in_range(start_anchor..end_anchor) -// .map(|anchor| { -// let start = anchor.start.to_point(&snapshot.buffer_snapshot); -// ( -// start.row, -// start.to_display_point(&snapshot.display_snapshot) -// ..anchor.end.to_display_point(&snapshot), -// ) -// }), -// ); - -// let mut newest_selection_head = None; - -// if editor.show_local_selections { -// let mut local_selections: Vec> = editor -// .selections -// .disjoint_in_range(start_anchor..end_anchor, cx); -// local_selections.extend(editor.selections.pending(cx)); -// let mut layouts = Vec::new(); -// let newest = editor.selections.newest(cx); -// for selection in local_selections.drain(..) { -// let is_empty = selection.start == selection.end; -// let is_newest = selection == newest; - -// let layout = SelectionLayout::new( -// selection, -// editor.selections.line_mode, -// editor.cursor_shape, -// &snapshot.display_snapshot, -// is_newest, -// true, -// ); -// if is_newest { -// newest_selection_head = Some(layout.head); -// } - -// for row in cmp::max(layout.active_rows.start, start_row) -// ..=cmp::min(layout.active_rows.end, end_row) -// { -// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); -// *contains_non_empty_selection |= !is_empty; -// } -// layouts.push(layout); -// } - -// selections.push((style.selection, layouts)); -// } - -// if let Some(collaboration_hub) = &editor.collaboration_hub { -// // When following someone, render the local selections in their color. -// if let Some(leader_id) = editor.leader_peer_id { -// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { -// if let Some(participant_index) = collaboration_hub -// .user_participant_indices(cx) -// .get(&collaborator.user_id) -// { -// if let Some((local_selection_style, _)) = selections.first_mut() { -// *local_selection_style = -// style.selection_style_for_room_participant(participant_index.0); -// } -// } -// } -// } - -// let mut remote_selections = HashMap::default(); -// for selection in snapshot.remote_selections_in_range( -// &(start_anchor..end_anchor), -// collaboration_hub.as_ref(), -// cx, -// ) { -// let selection_style = if let Some(participant_index) = selection.participant_index { -// style.selection_style_for_room_participant(participant_index.0) -// } else { -// style.absent_selection -// }; - -// // Don't re-render the leader's selections, since the local selections -// // match theirs. -// if Some(selection.peer_id) == editor.leader_peer_id { -// continue; -// } - -// remote_selections -// .entry(selection.replica_id) -// .or_insert((selection_style, Vec::new())) -// .1 -// .push(SelectionLayout::new( -// selection.selection, -// selection.line_mode, -// selection.cursor_shape, -// &snapshot.display_snapshot, -// false, -// false, -// )); -// } - -// selections.extend(remote_selections.into_values()); -// } - -// let scrollbar_settings = &settings::get::(cx).scrollbar; -// let show_scrollbars = match scrollbar_settings.show { -// ShowScrollbar::Auto => { -// // Git -// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) -// || -// // Selections -// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty) -// // Scrollmanager -// || editor.scroll_manager.scrollbars_visible() -// } -// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), -// ShowScrollbar::Always => true, -// ShowScrollbar::Never => false, -// }; - -// let fold_ranges: Vec<(BufferRow, Range, Color)> = fold_ranges -// .into_iter() -// .map(|(id, fold)| { -// let color = self -// .style -// .folds -// .ellipses -// .background -// .style_for(&mut cx.mouse_state::(id as usize)) -// .color; - -// (id, fold, color) -// }) -// .collect(); - -// let head_for_relative = newest_selection_head.unwrap_or_else(|| { -// let newest = editor.selections.newest::(cx); -// SelectionLayout::new( -// newest, -// editor.selections.line_mode, -// editor.cursor_shape, -// &snapshot.display_snapshot, -// true, -// true, -// ) -// .head -// }); - -// let (line_number_layouts, fold_statuses) = self.layout_line_numbers( -// start_row..end_row, -// &active_rows, -// head_for_relative, -// is_singleton, -// &snapshot, -// cx, -// ); - -// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); - -// let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); - -// let mut max_visible_line_width = 0.0; -// let line_layouts = -// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx); -// for line_with_invisibles in &line_layouts { -// if line_with_invisibles.line.width() > max_visible_line_width { -// max_visible_line_width = line_with_invisibles.line.width(); -// } -// } - -// let style = self.style.clone(); -// let longest_line_width = layout_line( -// snapshot.longest_row(), -// &snapshot, -// &style, -// cx.text_layout_cache(), -// ) -// .width(); -// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x; -// let em_width = style.text.em_width(cx.font_cache()); -// let (scroll_width, blocks) = self.layout_blocks( -// start_row..end_row, -// &snapshot, -// size.x, -// scroll_width, -// gutter_padding, -// gutter_width, -// em_width, -// gutter_width + gutter_margin, -// line_height, -// &style, -// &line_layouts, -// editor, -// cx, -// ); - -// let scroll_max = point( -// ((scroll_width - text_size.x) / em_width).max(0.0), -// max_row as f32, -// ); - -// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); - -// let autoscrolled = if autoscroll_horizontally { -// editor.autoscroll_horizontally( -// start_row, -// text_size.x, -// scroll_width, -// em_width, -// &line_layouts, -// cx, -// ) -// } else { -// false -// }; - -// if clamped || autoscrolled { -// snapshot = editor.snapshot(cx); -// } - -// let style = editor.style(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, style.clone(), cx); -// } - -// 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)); -// } -// } - -// let visible_rows = start_row..start_row + line_layouts.len() as u32; -// let mut hover = editor.hover_state.render( -// &snapshot, -// &style, -// visible_rows, -// editor.workspace.as_ref().map(|(w, _)| w.clone()), -// cx, -// ); -// let mode = editor.mode; - -// let mut fold_indicators = editor.render_fold_indicators( -// fold_statuses, -// &style, -// editor.gutter_hovered, -// line_height, -// gutter_margin, -// cx, -// ); - -// if let Some((_, context_menu)) = context_menu.as_mut() { -// context_menu.layout( -// SizeConstraint { -// min: gpui::Point::::zero(), -// max: point( -// cx.window_size().x * 0.7, -// (12. * line_height).min((size.y - line_height) / 2.), -// ), -// }, -// editor, -// cx, -// ); -// } - -// 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, -// ); -// } - -// for fold_indicator in fold_indicators.iter_mut() { -// if let Some(indicator) = fold_indicator.as_mut() { -// indicator.layout( -// SizeConstraint::strict_along( -// Axis::Vertical, -// line_height * style.code_actions.vertical_scale, -// ), -// editor, -// cx, -// ); -// } -// } - -// if let Some((_, hover_popovers)) = hover.as_mut() { -// for hover_popover in hover_popovers.iter_mut() { -// hover_popover.layout( -// SizeConstraint { -// min: gpui::Point::::zero(), -// max: point( -// (120. * em_width) // Default size -// .min(size.x / 2.) // Shrink to half of the editor width -// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters -// (16. * line_height) // Default size -// .min(size.y / 2.) // Shrink to half of the editor height -// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines -// ), -// }, -// editor, -// cx, -// ); -// } -// } - -// let invisible_symbol_font_size = self.style.text.font_size / 2.0; -// let invisible_symbol_style = RunStyle { -// color: self.style.whitespace, -// font_id: self.style.text.font_id, -// underline: Default::default(), -// }; - -// ( -// size, -// LayoutState { -// mode, -// position_map: Arc::new(PositionMap { -// size, -// scroll_max, -// line_layouts, -// line_height, -// em_width, -// em_advance, -// snapshot, -// }), -// visible_display_row_range: start_row..end_row, -// wrap_guides, -// gutter_size, -// gutter_padding, -// text_size, -// scrollbar_row_range, -// show_scrollbars, -// is_singleton, -// max_row, -// gutter_margin, -// active_rows, -// highlighted_rows, -// highlighted_ranges, -// fold_ranges, -// line_number_layouts, -// display_hunks, -// blocks, -// selections, -// context_menu, -// code_actions_indicator, -// fold_indicators, -// tab_invisible: cx.text_layout_cache().layout_str( -// "→", -// invisible_symbol_font_size, -// &[("→".len(), invisible_symbol_style)], -// ), -// space_invisible: cx.text_layout_cache().layout_str( -// "•", -// invisible_symbol_font_size, -// &[("•".len(), invisible_symbol_style)], -// ), -// hover_popovers: hover, -// }, -// ) -// } - -// fn paint( -// &mut self, -// bounds: Bounds, -// visible_bounds: Bounds, -// layout: &mut Self::LayoutState, -// editor: &mut Editor, -// cx: &mut ViewContext, -// ) -> Self::PaintState { -// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); -// cx.scene().push_layer(Some(visible_bounds)); - -// let gutter_bounds = Bounds::::new(bounds.origin, layout.gutter_size); -// let text_bounds = Bounds::::new( -// bounds.origin + point(layout.gutter_size.x, 0.0), -// layout.text_size, -// ); - -// Self::attach_mouse_handlers( -// &layout.position_map, -// layout.hover_popovers.is_some(), -// visible_bounds, -// text_bounds, -// gutter_bounds, -// bounds, -// cx, -// ); - -// self.paint_background(gutter_bounds, text_bounds, layout, cx); -// if layout.gutter_size.x > 0. { -// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx); -// } -// self.paint_text(text_bounds, visible_bounds, layout, editor, cx); - -// cx.scene().push_layer(Some(bounds)); -// if !layout.blocks.is_empty { -// self.paint_blocks(bounds, visible_bounds, layout, editor, cx); -// } -// self.paint_scrollbar(bounds, layout, &editor, cx); -// cx.scene().pop_layer(); -// cx.scene().pop_layer(); -// } - -// fn rect_for_text_range( -// &self, -// range_utf16: Range, -// bounds: Bounds, -// _: Bounds, -// layout: &Self::LayoutState, -// _: &Self::PaintState, -// _: &Editor, -// _: &ViewContext, -// ) -> Option> { -// let text_bounds = Bounds::::new( -// bounds.origin + point(layout.gutter_size.x, 0.0), -// layout.text_size, -// ); -// let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.); -// let scroll_position = layout.position_map.snapshot.scroll_position(); -// let start_row = scroll_position.y as u32; -// let scroll_top = scroll_position.y * layout.position_map.line_height; -// let scroll_left = scroll_position.x * layout.position_map.em_width; - -// let range_start = OffsetUtf16(range_utf16.start) -// .to_display_point(&layout.position_map.snapshot.display_snapshot); -// if range_start.row() < start_row { -// return None; -// } - -// let line = &layout -// .position_map -// .line_layouts -// .get((range_start.row() - start_row) as usize)? -// .line; -// let range_start_x = line.x_for_index(range_start.column() as usize); -// let range_start_y = range_start.row() as f32 * layout.position_map.line_height; -// Some(Bounds::::new( -// content_origin -// + point( -// range_start_x, -// range_start_y + layout.position_map.line_height, -// ) -// - point(scroll_left, scroll_top), -// point( -// layout.position_map.em_width, -// layout.position_map.line_height, -// ), -// )) -// } - -// fn debug( -// &self, -// bounds: Bounds, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// _: &Editor, -// _: &ViewContext, -// ) -> json::Value { -// json!({ -// "type": "BufferElement", -// "bounds": bounds.to_json() -// }) -// } -// } - type BufferRow = u32; pub struct LayoutState { @@ -4134,3 +3599,59 @@ pub fn register_action( } }) } + +fn compute_auto_height_layout( + editor: &mut Editor, + max_lines: usize, + max_line_number_width: Pixels, + known_dimensions: Size>, + cx: &mut ViewContext, +) -> Option> { + let mut width = known_dimensions.width?; + if let Some(height) = known_dimensions.height { + return Some(size(width, height)); + } + + let style = editor.style.as_ref().unwrap(); + let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_size = style.text.font_size.to_pixels(cx.rem_size()); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); + let em_width = cx + .text_system() + .typographic_bounds(font_id, font_size, 'm') + .unwrap() + .size + .width; + + let mut snapshot = editor.snapshot(cx); + let gutter_padding; + let gutter_width; + let gutter_margin; + if snapshot.show_gutter { + let descent = cx.text_system().descent(font_id, font_size).unwrap(); + let gutter_padding_factor = 3.5; + gutter_padding = (em_width * gutter_padding_factor).round(); + gutter_width = max_line_number_width + gutter_padding * 2.0; + gutter_margin = -descent; + } else { + gutter_padding = Pixels::ZERO; + gutter_width = Pixels::ZERO; + gutter_margin = Pixels::ZERO; + }; + + editor.gutter_width = gutter_width; + let text_width = width - gutter_width; + let overscroll = size(em_width, px(0.)); + + let editor_width = text_width - gutter_margin - overscroll.width - em_width; + if editor.set_wrap_width(Some(editor_width), cx) { + snapshot = editor.snapshot(cx); + } + + let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; + let height = scroll_height + .max(line_height) + .min(line_height * max_lines as f32); + + Some(size(width, height)) +} diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index e5b5e6f340..93bb37c622 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -4,13 +4,14 @@ use crate::{ EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context as _, Result}; use collections::HashSet; use futures::future::try_join_all; use gpui::{ - div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter, - FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, - ViewContext, VisualContext, WeakView, WindowContext, + div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Div, Entity, + EntityId, EventEmitter, FocusHandle, IntoElement, Model, ParentElement, Pixels, Render, + SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, @@ -20,6 +21,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat use rpc::proto::{self, update_view, PeerId}; use settings::Settings; use smallvec::SmallVec; +use std::fmt::Write; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -31,8 +33,11 @@ use std::{ use text::Selection; use theme::{ActiveTheme, Theme}; use ui::{Color, Label}; -use util::{paths::PathExt, ResultExt, TryFutureExt}; -use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}; +use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; +use workspace::{ + item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}, + StatusItemView, +}; use workspace::{ item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, @@ -71,110 +76,108 @@ impl FollowableItem for Editor { workspace: View, remote_id: ViewId, state: &mut Option, - cx: &mut AppContext, + cx: &mut WindowContext, ) -> Option>>> { - todo!() + let project = workspace.read(cx).project().to_owned(); + let Some(proto::view::Variant::Editor(_)) = state else { + return None; + }; + let Some(proto::view::Variant::Editor(state)) = state.take() else { + unreachable!() + }; + + let client = project.read(cx).client(); + let replica_id = project.read(cx).replica_id(); + let buffer_ids = state + .excerpts + .iter() + .map(|excerpt| excerpt.buffer_id) + .collect::>(); + let buffers = project.update(cx, |project, cx| { + buffer_ids + .iter() + .map(|id| project.open_buffer_by_id(*id, cx)) + .collect::>() + }); + + let pane = pane.downgrade(); + Some(cx.spawn(|mut cx| async move { + let mut buffers = futures::future::try_join_all(buffers).await?; + let editor = pane.update(&mut cx, |pane, cx| { + let mut editors = pane.items_of_type::(); + editors.find(|editor| { + let ids_match = editor.remote_id(&client, cx) == Some(remote_id); + let singleton_buffer_matches = state.singleton + && buffers.first() + == editor.read(cx).buffer.read(cx).as_singleton().as_ref(); + ids_match || singleton_buffer_matches + }) + })?; + + let editor = if let Some(editor) = editor { + editor + } else { + pane.update(&mut cx, |_, cx| { + let multibuffer = cx.build_model(|cx| { + let mut multibuffer; + if state.singleton && buffers.len() == 1 { + multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx) + } else { + multibuffer = MultiBuffer::new(replica_id); + let mut excerpts = state.excerpts.into_iter().peekable(); + while let Some(excerpt) = excerpts.peek() { + let buffer_id = excerpt.buffer_id; + let buffer_excerpts = iter::from_fn(|| { + let excerpt = excerpts.peek()?; + (excerpt.buffer_id == buffer_id) + .then(|| excerpts.next().unwrap()) + }); + let buffer = + buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id); + if let Some(buffer) = buffer { + multibuffer.push_excerpts( + buffer.clone(), + buffer_excerpts.filter_map(deserialize_excerpt_range), + cx, + ); + } + } + }; + + if let Some(title) = &state.title { + multibuffer = multibuffer.with_title(title.clone()) + } + + multibuffer + }); + + cx.build_view(|cx| { + let mut editor = + Editor::for_multibuffer(multibuffer, Some(project.clone()), cx); + editor.remote_id = Some(remote_id); + editor + }) + })? + }; + + update_editor_from_message( + editor.downgrade(), + project, + proto::update_view::Editor { + selections: state.selections, + pending_selection: state.pending_selection, + scroll_top_anchor: state.scroll_top_anchor, + scroll_x: state.scroll_x, + scroll_y: state.scroll_y, + ..Default::default() + }, + &mut cx, + ) + .await?; + + Ok(editor) + })) } - // let project = workspace.read(cx).project().to_owned(); - // let Some(proto::view::Variant::Editor(_)) = state else { - // return None; - // }; - // let Some(proto::view::Variant::Editor(state)) = state.take() else { - // unreachable!() - // }; - - // let client = project.read(cx).client(); - // let replica_id = project.read(cx).replica_id(); - // let buffer_ids = state - // .excerpts - // .iter() - // .map(|excerpt| excerpt.buffer_id) - // .collect::>(); - // let buffers = project.update(cx, |project, cx| { - // buffer_ids - // .iter() - // .map(|id| project.open_buffer_by_id(*id, cx)) - // .collect::>() - // }); - - // let pane = pane.downgrade(); - // Some(cx.spawn(|mut cx| async move { - // let mut buffers = futures::future::try_join_all(buffers).await?; - // let editor = pane.read_with(&cx, |pane, cx| { - // let mut editors = pane.items_of_type::(); - // editors.find(|editor| { - // let ids_match = editor.remote_id(&client, cx) == Some(remote_id); - // let singleton_buffer_matches = state.singleton - // && buffers.first() - // == editor.read(cx).buffer.read(cx).as_singleton().as_ref(); - // ids_match || singleton_buffer_matches - // }) - // })?; - - // let editor = if let Some(editor) = editor { - // editor - // } else { - // pane.update(&mut cx, |_, cx| { - // let multibuffer = cx.add_model(|cx| { - // let mut multibuffer; - // if state.singleton && buffers.len() == 1 { - // multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx) - // } else { - // multibuffer = MultiBuffer::new(replica_id); - // let mut excerpts = state.excerpts.into_iter().peekable(); - // while let Some(excerpt) = excerpts.peek() { - // let buffer_id = excerpt.buffer_id; - // let buffer_excerpts = iter::from_fn(|| { - // let excerpt = excerpts.peek()?; - // (excerpt.buffer_id == buffer_id) - // .then(|| excerpts.next().unwrap()) - // }); - // let buffer = - // buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id); - // if let Some(buffer) = buffer { - // multibuffer.push_excerpts( - // buffer.clone(), - // buffer_excerpts.filter_map(deserialize_excerpt_range), - // cx, - // ); - // } - // } - // }; - - // if let Some(title) = &state.title { - // multibuffer = multibuffer.with_title(title.clone()) - // } - - // multibuffer - // }); - - // cx.add_view(|cx| { - // let mut editor = - // Editor::for_multibuffer(multibuffer, Some(project.clone()), cx); - // editor.remote_id = Some(remote_id); - // editor - // }) - // })? - // }; - - // update_editor_from_message( - // editor.downgrade(), - // project, - // proto::update_view::Editor { - // selections: state.selections, - // pending_selection: state.pending_selection, - // scroll_top_anchor: state.scroll_top_anchor, - // scroll_x: state.scroll_x, - // scroll_y: state.scroll_y, - // ..Default::default() - // }, - // &mut cx, - // ) - // .await?; - - // Ok(editor) - // })) - // } fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext) { self.leader_peer_id = leader_peer_id; @@ -195,7 +198,7 @@ impl FollowableItem for Editor { cx.notify(); } - fn to_state_proto(&self, cx: &AppContext) -> Option { + fn to_state_proto(&self, cx: &WindowContext) -> Option { let buffer = self.buffer.read(cx); let scroll_anchor = self.scroll_manager.anchor(); let excerpts = buffer @@ -242,7 +245,7 @@ impl FollowableItem for Editor { &self, event: &Self::FollowableEvent, update: &mut Option, - cx: &AppContext, + cx: &WindowContext, ) -> bool { let update = update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default())); @@ -315,7 +318,7 @@ impl FollowableItem for Editor { }) } - fn is_project_item(&self, _cx: &AppContext) -> bool { + fn is_project_item(&self, _cx: &WindowContext) -> bool { true } } @@ -324,132 +327,129 @@ async fn update_editor_from_message( this: WeakView, project: Model, message: proto::update_view::Editor, - cx: &mut AsyncAppContext, + cx: &mut AsyncWindowContext, ) -> Result<()> { - todo!() + // Open all of the buffers of which excerpts were added to the editor. + let inserted_excerpt_buffer_ids = message + .inserted_excerpts + .iter() + .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id)) + .collect::>(); + let inserted_excerpt_buffers = project.update(cx, |project, cx| { + inserted_excerpt_buffer_ids + .into_iter() + .map(|id| project.open_buffer_by_id(id, cx)) + .collect::>() + })?; + let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?; + + // Update the editor's excerpts. + this.update(cx, |editor, cx| { + editor.buffer.update(cx, |multibuffer, cx| { + let mut removed_excerpt_ids = message + .deleted_excerpts + .into_iter() + .map(ExcerptId::from_proto) + .collect::>(); + removed_excerpt_ids.sort_by({ + let multibuffer = multibuffer.read(cx); + move |a, b| a.cmp(&b, &multibuffer) + }); + + let mut insertions = message.inserted_excerpts.into_iter().peekable(); + while let Some(insertion) = insertions.next() { + let Some(excerpt) = insertion.excerpt else { + continue; + }; + let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { + continue; + }; + let buffer_id = excerpt.buffer_id; + let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else { + continue; + }; + + let adjacent_excerpts = iter::from_fn(|| { + let insertion = insertions.peek()?; + if insertion.previous_excerpt_id.is_none() + && insertion.excerpt.as_ref()?.buffer_id == buffer_id + { + insertions.next()?.excerpt + } else { + None + } + }); + + multibuffer.insert_excerpts_with_ids_after( + ExcerptId::from_proto(previous_excerpt_id), + buffer, + [excerpt] + .into_iter() + .chain(adjacent_excerpts) + .filter_map(|excerpt| { + Some(( + ExcerptId::from_proto(excerpt.id), + deserialize_excerpt_range(excerpt)?, + )) + }), + cx, + ); + } + + multibuffer.remove_excerpts(removed_excerpt_ids, cx); + }); + })?; + + // Deserialize the editor state. + let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| { + let buffer = editor.buffer.read(cx).read(cx); + let selections = message + .selections + .into_iter() + .filter_map(|selection| deserialize_selection(&buffer, selection)) + .collect::>(); + let pending_selection = message + .pending_selection + .and_then(|selection| deserialize_selection(&buffer, selection)); + let scroll_top_anchor = message + .scroll_top_anchor + .and_then(|anchor| deserialize_anchor(&buffer, anchor)); + anyhow::Ok((selections, pending_selection, scroll_top_anchor)) + })??; + + // Wait until the buffer has received all of the operations referenced by + // the editor's new state. + this.update(cx, |editor, cx| { + editor.buffer.update(cx, |buffer, cx| { + buffer.wait_for_anchors( + selections + .iter() + .chain(pending_selection.as_ref()) + .flat_map(|selection| [selection.start, selection.end]) + .chain(scroll_top_anchor), + cx, + ) + }) + })? + .await?; + + // Update the editor's state. + this.update(cx, |editor, cx| { + if !selections.is_empty() || pending_selection.is_some() { + editor.set_selections_from_remote(selections, pending_selection, cx); + editor.request_autoscroll_remotely(Autoscroll::newest(), cx); + } else if let Some(scroll_top_anchor) = scroll_top_anchor { + editor.set_scroll_anchor_remote( + ScrollAnchor { + anchor: scroll_top_anchor, + offset: point(message.scroll_x, message.scroll_y), + }, + cx, + ); + } + })?; + Ok(()) } -// Previous implementation of the above -// // Open all of the buffers of which excerpts were added to the editor. -// let inserted_excerpt_buffer_ids = message -// .inserted_excerpts -// .iter() -// .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id)) -// .collect::>(); -// let inserted_excerpt_buffers = project.update(cx, |project, cx| { -// inserted_excerpt_buffer_ids -// .into_iter() -// .map(|id| project.open_buffer_by_id(id, cx)) -// .collect::>() -// })?; -// let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?; - -// // Update the editor's excerpts. -// this.update(cx, |editor, cx| { -// editor.buffer.update(cx, |multibuffer, cx| { -// let mut removed_excerpt_ids = message -// .deleted_excerpts -// .into_iter() -// .map(ExcerptId::from_proto) -// .collect::>(); -// removed_excerpt_ids.sort_by({ -// let multibuffer = multibuffer.read(cx); -// move |a, b| a.cmp(&b, &multibuffer) -// }); - -// let mut insertions = message.inserted_excerpts.into_iter().peekable(); -// while let Some(insertion) = insertions.next() { -// let Some(excerpt) = insertion.excerpt else { -// continue; -// }; -// let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { -// continue; -// }; -// let buffer_id = excerpt.buffer_id; -// let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else { -// continue; -// }; - -// let adjacent_excerpts = iter::from_fn(|| { -// let insertion = insertions.peek()?; -// if insertion.previous_excerpt_id.is_none() -// && insertion.excerpt.as_ref()?.buffer_id == buffer_id -// { -// insertions.next()?.excerpt -// } else { -// None -// } -// }); - -// multibuffer.insert_excerpts_with_ids_after( -// ExcerptId::from_proto(previous_excerpt_id), -// buffer, -// [excerpt] -// .into_iter() -// .chain(adjacent_excerpts) -// .filter_map(|excerpt| { -// Some(( -// ExcerptId::from_proto(excerpt.id), -// deserialize_excerpt_range(excerpt)?, -// )) -// }), -// cx, -// ); -// } - -// multibuffer.remove_excerpts(removed_excerpt_ids, cx); -// }); -// })?; - -// // Deserialize the editor state. -// let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| { -// let buffer = editor.buffer.read(cx).read(cx); -// let selections = message -// .selections -// .into_iter() -// .filter_map(|selection| deserialize_selection(&buffer, selection)) -// .collect::>(); -// let pending_selection = message -// .pending_selection -// .and_then(|selection| deserialize_selection(&buffer, selection)); -// let scroll_top_anchor = message -// .scroll_top_anchor -// .and_then(|anchor| deserialize_anchor(&buffer, anchor)); -// anyhow::Ok((selections, pending_selection, scroll_top_anchor)) -// })??; - -// // Wait until the buffer has received all of the operations referenced by -// // the editor's new state. -// this.update(cx, |editor, cx| { -// editor.buffer.update(cx, |buffer, cx| { -// buffer.wait_for_anchors( -// selections -// .iter() -// .chain(pending_selection.as_ref()) -// .flat_map(|selection| [selection.start, selection.end]) -// .chain(scroll_top_anchor), -// cx, -// ) -// }) -// })? -// .await?; - -// // Update the editor's state. -// this.update(cx, |editor, cx| { -// if !selections.is_empty() || pending_selection.is_some() { -// editor.set_selections_from_remote(selections, pending_selection, cx); -// editor.request_autoscroll_remotely(Autoscroll::newest(), cx); -// } else if let Some(scroll_top_anchor) = scroll_top_anchor { -// editor.set_scroll_anchor_remote( -// ScrollAnchor { -// anchor: scroll_top_anchor, -// offset: point(message.scroll_x, message.scroll_y), -// }, -// cx, -// ); -// } -// })?; -// Ok(()) -// } fn serialize_excerpt( buffer_id: u64, @@ -529,39 +529,38 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) impl Item for Editor { fn navigate(&mut self, data: Box, cx: &mut ViewContext) -> bool { - todo!(); - // if let Ok(data) = data.downcast::() { - // let newest_selection = self.selections.newest::(cx); - // let buffer = self.buffer.read(cx).read(cx); - // let offset = if buffer.can_resolve(&data.cursor_anchor) { - // data.cursor_anchor.to_point(&buffer) - // } else { - // buffer.clip_point(data.cursor_position, Bias::Left) - // }; + if let Ok(data) = data.downcast::() { + let newest_selection = self.selections.newest::(cx); + let buffer = self.buffer.read(cx).read(cx); + let offset = if buffer.can_resolve(&data.cursor_anchor) { + data.cursor_anchor.to_point(&buffer) + } else { + buffer.clip_point(data.cursor_position, Bias::Left) + }; - // let mut scroll_anchor = data.scroll_anchor; - // if !buffer.can_resolve(&scroll_anchor.anchor) { - // scroll_anchor.anchor = buffer.anchor_before( - // buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left), - // ); - // } + let mut scroll_anchor = data.scroll_anchor; + if !buffer.can_resolve(&scroll_anchor.anchor) { + scroll_anchor.anchor = buffer.anchor_before( + buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left), + ); + } - // drop(buffer); + drop(buffer); - // if newest_selection.head() == offset { - // false - // } else { - // let nav_history = self.nav_history.take(); - // self.set_scroll_anchor(scroll_anchor, cx); - // self.change_selections(Some(Autoscroll::fit()), cx, |s| { - // s.select_ranges([offset..offset]) - // }); - // self.nav_history = nav_history; - // true - // } - // } else { - // false - // } + if newest_selection.head() == offset { + false + } else { + let nav_history = self.nav_history.take(); + self.set_scroll_anchor(scroll_anchor, cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([offset..offset]) + }); + self.nav_history = nav_history; + true + } + } else { + false + } } fn tab_tooltip_text(&self, cx: &AppContext) -> Option { @@ -768,7 +767,7 @@ impl Item for Editor { let cursor = self.selections.newest_anchor().head(); let multibuffer = &self.buffer().read(cx); let (buffer_id, symbols) = - multibuffer.symbols_containing(cursor, Some(&cx.theme().styles.syntax), cx)?; + multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?; let buffer = multibuffer.buffer(buffer_id)?; let buffer = buffer.read(cx); @@ -788,19 +787,9 @@ impl Item for Editor { text: filename, highlights: None, }]; - breadcrumbs.extend(symbols.into_iter().map(|symbol| { - // eprintln!( - // "ranges: {:?}", - // symbol - // .highlight_ranges - // .iter() - // .map(|range| &symbol.text[range.0.clone()]) - // .collect::>() - // ); - BreadcrumbText { - text: symbol.text, - highlights: Some(symbol.highlight_ranges), - } + breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText { + text: symbol.text, + highlights: Some(symbol.highlight_ranges), })); Some(breadcrumbs) } @@ -1129,86 +1118,78 @@ pub struct CursorPosition { _observe_active_editor: Option, } -// impl Default for CursorPosition { -// fn default() -> Self { -// Self::new() -// } -// } +impl Default for CursorPosition { + fn default() -> Self { + Self::new() + } +} -// impl CursorPosition { -// pub fn new() -> Self { -// Self { -// position: None, -// selected_count: 0, -// _observe_active_editor: None, -// } -// } +impl CursorPosition { + pub fn new() -> Self { + Self { + position: None, + selected_count: 0, + _observe_active_editor: None, + } + } -// fn update_position(&mut self, editor: View, cx: &mut ViewContext) { -// let editor = editor.read(cx); -// let buffer = editor.buffer().read(cx).snapshot(cx); + fn update_position(&mut self, editor: View, cx: &mut ViewContext) { + let editor = editor.read(cx); + let buffer = editor.buffer().read(cx).snapshot(cx); -// self.selected_count = 0; -// let mut last_selection: Option> = None; -// for selection in editor.selections.all::(cx) { -// self.selected_count += selection.end - selection.start; -// if last_selection -// .as_ref() -// .map_or(true, |last_selection| selection.id > last_selection.id) -// { -// last_selection = Some(selection); -// } -// } -// self.position = last_selection.map(|s| s.head().to_point(&buffer)); + self.selected_count = 0; + let mut last_selection: Option> = None; + for selection in editor.selections.all::(cx) { + self.selected_count += selection.end - selection.start; + if last_selection + .as_ref() + .map_or(true, |last_selection| selection.id > last_selection.id) + { + last_selection = Some(selection); + } + } + self.position = last_selection.map(|s| s.head().to_point(&buffer)); -// cx.notify(); -// } -// } + cx.notify(); + } +} -// impl Entity for CursorPosition { -// type Event = (); -// } +impl Render for CursorPosition { + type Element = Div; -// impl View for CursorPosition { -// fn ui_name() -> &'static str { -// "CursorPosition" -// } + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div().when_some(self.position, |el, position| { + let mut text = format!( + "{}{FILE_ROW_COLUMN_DELIMITER}{}", + position.row + 1, + position.column + 1 + ); + if self.selected_count > 0 { + write!(text, " ({} selected)", self.selected_count).unwrap(); + } -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// if let Some(position) = self.position { -// let theme = &theme::current(cx).workspace.status_bar; -// let mut text = format!( -// "{}{FILE_ROW_COLUMN_DELIMITER}{}", -// position.row + 1, -// position.column + 1 -// ); -// if self.selected_count > 0 { -// write!(text, " ({} selected)", self.selected_count).unwrap(); -// } -// Label::new(text, theme.cursor_position.clone()).into_any() -// } else { -// Empty::new().into_any() -// } -// } -// } + el.child(Label::new(text)) + }) + } +} -// impl StatusItemView for CursorPosition { -// fn set_active_pane_item( -// &mut self, -// active_pane_item: Option<&dyn ItemHandle>, -// cx: &mut ViewContext, -// ) { -// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(cx)) { -// self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); -// self.update_position(editor, cx); -// } else { -// self.position = None; -// self._observe_active_editor = None; -// } +impl StatusItemView for CursorPosition { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) { + if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(cx)) { + self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); + self.update_position(editor, cx); + } else { + self.position = None; + self._observe_active_editor = None; + } -// cx.notify(); -// } -// } + cx.notify(); + } +} fn path_for_buffer<'a>( buffer: &Model, diff --git a/crates/editor2/src/mouse_context_menu.rs b/crates/editor2/src/mouse_context_menu.rs index b70a826bf8..fdeec9110b 100644 --- a/crates/editor2/src/mouse_context_menu.rs +++ b/crates/editor2/src/mouse_context_menu.rs @@ -1,5 +1,14 @@ -use crate::{DisplayPoint, Editor, EditorMode, SelectMode}; -use gpui::{Pixels, Point, ViewContext}; +use crate::{ + DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition, + Rename, RevealInFinder, SelectMode, ToggleCodeActions, +}; +use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext}; + +pub struct MouseContextMenu { + pub(crate) position: Point, + pub(crate) context_menu: View, + _subscription: Subscription, +} pub fn deploy_context_menu( editor: &mut Editor, @@ -7,50 +16,57 @@ pub fn deploy_context_menu( point: DisplayPoint, cx: &mut ViewContext, ) { - todo!(); + if !editor.is_focused(cx) { + editor.focus(cx); + } - // if !editor.focused { - // cx.focus_self(); - // } + // Don't show context menu for inline editors + if editor.mode() != EditorMode::Full { + return; + } - // // Don't show context menu for inline editors - // if editor.mode() != EditorMode::Full { - // return; - // } + // Don't show the context menu if there isn't a project associated with this editor + if editor.project.is_none() { + return; + } - // // Don't show the context menu if there isn't a project associated with this editor - // if editor.project.is_none() { - // return; - // } + // Move the cursor to the clicked location so that dispatched actions make sense + editor.change_selections(None, cx, |s| { + s.clear_disjoint(); + s.set_pending_display_range(point..point, SelectMode::Character); + }); - // // Move the cursor to the clicked location so that dispatched actions make sense - // editor.change_selections(None, cx, |s| { - // s.clear_disjoint(); - // s.set_pending_display_range(point..point, SelectMode::Character); - // }); + let context_menu = ui::ContextMenu::build(cx, |menu, cx| { + menu.action("Rename Symbol", Box::new(Rename), cx) + .action("Go to Definition", Box::new(GoToDefinition), cx) + .action("Go to Type Definition", Box::new(GoToTypeDefinition), cx) + .action("Find All References", Box::new(FindAllReferences), cx) + .action( + "Code Actions", + Box::new(ToggleCodeActions { + deployed_from_indicator: false, + }), + cx, + ) + .separator() + .action("Reveal in Finder", Box::new(RevealInFinder), cx) + }); + let context_menu_focus = context_menu.focus_handle(cx); + cx.focus(&context_menu_focus); - // editor.mouse_context_menu.update(cx, |menu, cx| { - // menu.show( - // position, - // AnchorCorner::TopLeft, - // vec![ - // ContextMenuItem::action("Rename Symbol", Rename), - // ContextMenuItem::action("Go to Definition", GoToDefinition), - // ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition), - // ContextMenuItem::action("Find All References", FindAllReferences), - // ContextMenuItem::action( - // "Code Actions", - // ToggleCodeActions { - // deployed_from_indicator: false, - // }, - // ), - // ContextMenuItem::Separator, - // ContextMenuItem::action("Reveal in Finder", RevealInFinder), - // ], - // cx, - // ); - // }); - // cx.notify(); + let _subscription = cx.subscribe(&context_menu, move |this, _, event: &DismissEvent, cx| { + this.mouse_context_menu.take(); + if context_menu_focus.contains_focused(cx) { + this.focus(cx); + } + }); + + editor.mouse_context_menu = Some(MouseContextMenu { + position, + context_menu, + _subscription, + }); + cx.notify(); } // #[cfg(test)] diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs index 6542ace5fb..8d71916210 100644 --- a/crates/editor2/src/selections_collection.rs +++ b/crates/editor2/src/selections_collection.rs @@ -315,14 +315,11 @@ impl SelectionsCollection { let line = display_map.layout_row(row, &text_layout_details); - dbg!("****START COL****"); let start_col = line.closest_index_for_x(positions.start) as u32; if start_col < line_len || (is_empty && positions.start == line.width) { let start = DisplayPoint::new(row, start_col); - dbg!("****END COL****"); let end_col = line.closest_index_for_x(positions.end) as u32; let end = DisplayPoint::new(row, end_col); - dbg!(start_col, end_col); Some(Selection { id: post_inc(&mut self.next_selection_id), diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 7a00b2644a..9938b94edb 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -15,7 +15,7 @@ use std::{ }, }; use text::Point; -use ui::{v_stack, HighlightedLabel, ListItem}; +use ui::{prelude::*, v_stack, HighlightedLabel, ListItem}; use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use workspace::Workspace; @@ -1256,7 +1256,7 @@ mod tests { // // TODO: without closing, the opened items do not propagate their history changes for some reason // it does work in real app though, only tests do not propagate. - workspace.update(cx, |_, cx| dbg!(cx.focused())); + workspace.update(cx, |_, cx| cx.focused()); let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await; assert!( diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9637720a67..c915753749 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -2,8 +2,8 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, - TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle, - WindowOptions, + TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext, + WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -502,6 +502,19 @@ impl<'a> VisualTestContext<'a> { self.cx.dispatch_action(self.window, action) } + pub fn window_title(&mut self) -> Option { + self.cx + .update_window(self.window, |_, cx| { + cx.window + .platform_window + .as_test() + .unwrap() + .window_title + .clone() + }) + .unwrap() + } + pub fn simulate_keystrokes(&mut self, keystrokes: &str) { self.cx.simulate_keystrokes(self.window, keystrokes) } @@ -509,6 +522,39 @@ impl<'a> VisualTestContext<'a> { pub fn simulate_input(&mut self, input: &str) { self.cx.simulate_input(self.window, input) } + + pub fn simulate_activation(&mut self) { + self.simulate_window_events(&mut |handlers| { + handlers + .active_status_change + .iter_mut() + .for_each(|f| f(true)); + }) + } + + pub fn simulate_deactivation(&mut self) { + self.simulate_window_events(&mut |handlers| { + handlers + .active_status_change + .iter_mut() + .for_each(|f| f(false)); + }) + } + + fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) { + let handlers = self + .cx + .update_window(self.window, |_, cx| { + cx.window + .platform_window + .as_test() + .unwrap() + .handlers + .clone() + }) + .unwrap(); + f(&mut *handlers.lock()); + } } impl<'a> Context for VisualTestContext<'a> { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index aebec237c7..d398b1f8fe 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -166,7 +166,6 @@ impl TextState { runs: Option>, cx: &mut WindowContext, ) -> LayoutId { - let text_system = cx.text_system().clone(); let text_style = cx.text_style(); let font_size = text_style.font_size.to_pixels(cx.rem_size()); let line_height = text_style @@ -174,18 +173,16 @@ impl TextState { .to_pixels(font_size.into(), cx.rem_size()); let text = SharedString::from(text); - let rem_size = cx.rem_size(); - let runs = if let Some(runs) = runs { runs } else { vec![text_style.to_run(text.len())] }; - let layout_id = cx.request_measured_layout(Default::default(), rem_size, { + let layout_id = cx.request_measured_layout(Default::default(), { let element_state = self.clone(); - move |known_dimensions, available_space| { + move |known_dimensions, available_space, cx| { let wrap_width = if text_style.white_space == WhiteSpace::Normal { known_dimensions.width.or(match available_space.width { crate::AvailableSpace::Definite(x) => Some(x), @@ -203,7 +200,8 @@ impl TextState { } } - let Some(lines) = text_system + let Some(lines) = cx + .text_system() .shape_text( &text, font_size, &runs, wrap_width, // Wrap if we know the width. ) diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 2d5a46f3d9..d8f4cc6804 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -109,7 +109,6 @@ impl Element for UniformList { cx: &mut WindowContext, ) -> (LayoutId, Self::State) { let max_items = self.item_count; - let rem_size = cx.rem_size(); let item_size = state .as_ref() .map(|s| s.item_size) @@ -120,9 +119,7 @@ impl Element for UniformList { .layout(state.map(|s| s.interactive), cx, |style, cx| { cx.request_measured_layout( style, - rem_size, - move |known_dimensions: Size>, - available_space: Size| { + move |known_dimensions, available_space, _cx| { let desired_height = item_size.height * max_items; let width = known_dimensions diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index d32c2e849b..20afd2d288 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -655,6 +655,20 @@ pub struct Corners { pub bottom_left: T, } +impl Corners +where + T: Clone + Default + Debug, +{ + pub fn all(value: T) -> Self { + Self { + top_left: value.clone(), + top_right: value.clone(), + bottom_right: value.clone(), + bottom_left: value, + } + } +} + impl Corners { pub fn to_pixels(&self, size: Size, rem_size: Pixels) -> Corners { let max = size.width.max(size.height) / 2.; diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 3027c05fbd..7375f47939 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow { fn draw(&self, scene: Scene); fn sprite_atlas(&self) -> Arc; + + #[cfg(any(test, feature = "test-support"))] + fn as_test(&self) -> Option<&TestWindow> { + None + } } pub trait PlatformDispatcher: Send + Sync { diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index df1ed9b2a6..4532b33f50 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -189,13 +189,9 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_become_active(&self, _callback: Box) { - unimplemented!() - } + fn on_become_active(&self, _callback: Box) {} - fn on_resign_active(&self, _callback: Box) { - unimplemented!() - } + fn on_resign_active(&self, _callback: Box) {} fn on_quit(&self, _callback: Box) {} diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index e355c3aa4b..2ad54eff0d 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -11,19 +11,20 @@ use std::{ }; #[derive(Default)] -struct Handlers { - active_status_change: Vec>, - input: Vec bool>>, - moved: Vec>, - resize: Vec, f32)>>, +pub(crate) struct TestWindowHandlers { + pub(crate) active_status_change: Vec>, + pub(crate) input: Vec bool>>, + pub(crate) moved: Vec>, + pub(crate) resize: Vec, f32)>>, } pub struct TestWindow { bounds: WindowBounds, current_scene: Mutex>, display: Rc, + pub(crate) window_title: Option, pub(crate) input_handler: Option>>>, - handlers: Mutex, + pub(crate) handlers: Arc>, platform: Weak, sprite_atlas: Arc, } @@ -42,6 +43,7 @@ impl TestWindow { input_handler: None, sprite_atlas: Arc::new(TestAtlas::new()), handlers: Default::default(), + window_title: Default::default(), } } } @@ -100,8 +102,8 @@ impl PlatformWindow for TestWindow { todo!() } - fn set_title(&mut self, _title: &str) { - todo!() + fn set_title(&mut self, title: &str) { + self.window_title = Some(title.to_owned()); } fn set_edited(&mut self, _edited: bool) { @@ -167,6 +169,10 @@ impl PlatformWindow for TestWindow { fn sprite_atlas(&self) -> sync::Arc { self.sprite_atlas.clone() } + + fn as_test(&self) -> Option<&TestWindow> { + Some(self) + } } pub struct TestAtlasState { diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 81a057055a..2bceb1bc13 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -1,4 +1,7 @@ -use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style}; +use crate::{ + AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, + WindowContext, +}; use collections::{HashMap, HashSet}; use smallvec::SmallVec; use std::fmt::Debug; @@ -9,13 +12,21 @@ use taffy::{ Taffy, }; -type Measureable = dyn Fn(Size>, Size) -> Size + Send + Sync; - pub struct TaffyLayoutEngine { - taffy: Taffy>, + taffy: Taffy, children_to_parents: HashMap, absolute_layout_bounds: HashMap>, computed_layouts: HashSet, + nodes_to_measure: HashMap< + LayoutId, + Box< + dyn FnMut( + Size>, + Size, + &mut WindowContext, + ) -> Size, + >, + >, } static EXPECT_MESSAGE: &'static str = @@ -28,6 +39,7 @@ impl TaffyLayoutEngine { children_to_parents: HashMap::default(), absolute_layout_bounds: HashMap::default(), computed_layouts: HashSet::default(), + nodes_to_measure: HashMap::default(), } } @@ -36,6 +48,7 @@ impl TaffyLayoutEngine { self.children_to_parents.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); + self.nodes_to_measure.clear(); } pub fn request_layout( @@ -65,18 +78,18 @@ impl TaffyLayoutEngine { &mut self, style: Style, rem_size: Pixels, - measure: impl Fn(Size>, Size) -> Size - + Send - + Sync + measure: impl FnMut(Size>, Size, &mut WindowContext) -> Size + 'static, ) -> LayoutId { let style = style.to_taffy(rem_size); - let measurable = Box::new(measure); - self.taffy - .new_leaf_with_context(style, measurable) + let layout_id = self + .taffy + .new_leaf_with_context(style, ()) .expect(EXPECT_MESSAGE) - .into() + .into(); + self.nodes_to_measure.insert(layout_id, Box::new(measure)); + layout_id } // Used to understand performance @@ -126,7 +139,12 @@ impl TaffyLayoutEngine { Ok(edges) } - pub fn compute_layout(&mut self, id: LayoutId, available_space: Size) { + pub fn compute_layout( + &mut self, + id: LayoutId, + available_space: Size, + cx: &mut WindowContext, + ) { // 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)?); @@ -159,8 +177,8 @@ impl TaffyLayoutEngine { .compute_layout_with_measure( id.into(), available_space.into(), - |known_dimensions, available_space, _node_id, context| { - let Some(measure) = context else { + |known_dimensions, available_space, node_id, _context| { + let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else { return taffy::geometry::Size::default(); }; @@ -169,10 +187,11 @@ impl TaffyLayoutEngine { height: known_dimensions.height.map(Pixels), }; - measure(known_dimensions, available_space.into()).into() + measure(known_dimensions, available_space.into(), cx).into() }, ) .expect(EXPECT_MESSAGE); + // println!("compute_layout took {:?}", started_at.elapsed()); } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index f31b0ae753..280c52df2a 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -209,9 +209,7 @@ impl AnyView { ) { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, rendered_element) = (self.layout)(self, cx); - cx.window - .layout_engine - .compute_layout(layout_id, available_space); + cx.compute_layout(layout_id, available_space); (self.paint)(self, rendered_element, cx); }) } @@ -240,6 +238,10 @@ impl Element for AnyView { } fn paint(self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + debug_assert!( + state.is_some(), + "state is None. Did you include an AnyView twice in the tree?" + ); (self.paint)(&self, state.take().unwrap(), cx) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 76932f28e4..5724f1e070 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -209,7 +209,7 @@ pub struct Window { sprite_atlas: Arc, rem_size: Pixels, viewport_size: Size, - pub(crate) layout_engine: TaffyLayoutEngine, + layout_engine: Option, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, pub(crate) previous_frame: Frame, @@ -327,7 +327,7 @@ impl Window { sprite_atlas, rem_size: px(16.), viewport_size: content_size, - layout_engine: TaffyLayoutEngine::new(), + layout_engine: Some(TaffyLayoutEngine::new()), root_view: None, element_id_stack: GlobalElementId::default(), previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), @@ -606,9 +606,11 @@ impl<'a> WindowContext<'a> { self.app.layout_id_buffer.extend(children.into_iter()); let rem_size = self.rem_size(); - self.window - .layout_engine - .request_layout(style, rem_size, &self.app.layout_id_buffer) + self.window.layout_engine.as_mut().unwrap().request_layout( + style, + rem_size, + &self.app.layout_id_buffer, + ) } /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children, @@ -618,22 +620,25 @@ impl<'a> WindowContext<'a> { /// The given closure is invoked at layout time with the known dimensions and available space and /// returns a `Size`. pub fn request_measured_layout< - F: Fn(Size>, Size) -> Size + Send + Sync + 'static, + F: FnMut(Size>, Size, &mut WindowContext) -> Size + + 'static, >( &mut self, style: Style, - rem_size: Pixels, measure: F, ) -> LayoutId { + let rem_size = self.rem_size(); self.window .layout_engine + .as_mut() + .unwrap() .request_measured_layout(style, rem_size, measure) } pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { - self.window - .layout_engine - .compute_layout(layout_id, available_space) + let mut layout_engine = self.window.layout_engine.take().unwrap(); + layout_engine.compute_layout(layout_id, available_space, self); + self.window.layout_engine = Some(layout_engine); } /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not @@ -643,6 +648,8 @@ impl<'a> WindowContext<'a> { let mut bounds = self .window .layout_engine + .as_mut() + .unwrap() .layout_bounds(layout_id) .map(Into::into); bounds.origin += self.element_offset(); @@ -678,6 +685,10 @@ impl<'a> WindowContext<'a> { self.window.platform_window.zoom(); } + pub fn set_window_title(&mut self, title: &str) { + self.window.platform_window.set_title(title); + } + pub fn display(&self) -> Option> { self.platform .displays() @@ -1189,7 +1200,7 @@ impl<'a> WindowContext<'a> { self.text_system().start_frame(); let window = &mut *self.window; - window.layout_engine.clear(); + window.layout_engine.as_mut().unwrap().clear(); mem::swap(&mut window.previous_frame, &mut window.current_frame); let frame = &mut window.current_frame; diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index eb124bfca2..875d4d4f83 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1627,9 +1627,21 @@ impl View for ProjectPanel { } } - fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { + fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) { Self::reset_to_default_keymap_context(keymap); keymap.add_identifier("menu"); + + if let Some(window) = cx.active_window() { + window.read_with(cx, |cx| { + let identifier = if self.filename_editor.is_focused(cx) { + "editing" + } else { + "not_editing" + }; + + keymap.add_identifier(identifier); + }); + } } fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index dc584d52ff..0a5a63f14a 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -10,9 +10,9 @@ use anyhow::{anyhow, Result}; use gpui::{ actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, - InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, - PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, - ViewContext, VisualContext as _, WeakView, WindowContext, + InteractiveElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, + Point, PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, + View, ViewContext, VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -29,8 +29,7 @@ use std::{ path::Path, sync::Arc, }; -use theme::ActiveTheme as _; -use ui::{v_stack, ContextMenu, IconElement, Label, ListItem}; +use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1421,6 +1420,22 @@ impl ProjectPanel { // ); // }) } + + fn dispatch_context(&self, cx: &ViewContext) -> KeyContext { + let mut dispatch_context = KeyContext::default(); + dispatch_context.add("ProjectPanel"); + dispatch_context.add("menu"); + + let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) { + "editing" + } else { + "not_editing" + }; + + dispatch_context.add(identifier); + + dispatch_context + } } impl Render for ProjectPanel { @@ -1434,7 +1449,7 @@ impl Render for ProjectPanel { .id("project-panel") .size_full() .relative() - .key_context("ProjectPanel") + .key_context(self.dispatch_context(cx)) .on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::expand_selected_entry)) @@ -2845,7 +2860,7 @@ mod tests { let worktree = worktree.read(cx); if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) { let entry_id = worktree.entry_for_path(relative_path).unwrap().id; - panel.selection = Some(Selection { + panel.selection = Some(crate::Selection { worktree_id: worktree.id(), entry_id, }); diff --git a/crates/search2/src/search.rs b/crates/search2/src/search.rs index 65a4ddfd42..13def6b4a7 100644 --- a/crates/search2/src/search.rs +++ b/crates/search2/src/search.rs @@ -4,7 +4,7 @@ use gpui::{actions, Action, AppContext, IntoElement}; pub use mode::SearchMode; use project::search::SearchQuery; use ui::prelude::*; -use ui::{ButtonStyle2, Icon, IconButton}; +use ui::{ButtonStyle, Icon, IconButton}; //pub use project_search::{ProjectSearchBar, ProjectSearchView}; // use theme::components::{ // action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle, @@ -91,8 +91,8 @@ impl SearchOptions { cx.dispatch_action(action.boxed_clone()); } }) - .style(ButtonStyle2::Subtle) - .when(active, |button| button.style(ButtonStyle2::Filled)) + .style(ButtonStyle::Subtle) + .when(active, |button| button.style(ButtonStyle::Filled)) } } @@ -103,8 +103,8 @@ fn toggle_replace_button(active: bool) -> impl IntoElement { cx.dispatch_action(Box::new(ToggleReplace)); cx.notify(); }) - .style(ButtonStyle2::Subtle) - .when(active, |button| button.style(ButtonStyle2::Filled)) + .style(ButtonStyle::Subtle) + .when(active, |button| button.style(ButtonStyle::Filled)) } fn render_replace_button( diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index 0eaf3d126c..2d63d1d491 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,3 +1,4 @@ +mod auto_height_editor; mod focus; mod kitchen_sink; mod picker; @@ -5,6 +6,7 @@ mod scroll; mod text; mod z_index; +pub use auto_height_editor::*; pub use focus::*; pub use kitchen_sink::*; pub use picker::*; diff --git a/crates/storybook2/src/stories/auto_height_editor.rs b/crates/storybook2/src/stories/auto_height_editor.rs new file mode 100644 index 0000000000..2f3089a4e6 --- /dev/null +++ b/crates/storybook2/src/stories/auto_height_editor.rs @@ -0,0 +1,34 @@ +use editor::Editor; +use gpui::{ + div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext, + WindowContext, +}; + +pub struct AutoHeightEditorStory { + editor: View, +} + +impl AutoHeightEditorStory { + pub fn new(cx: &mut WindowContext) -> View { + cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]); + cx.build_view(|cx| Self { + editor: cx.build_view(|cx| { + let mut editor = Editor::auto_height(3, cx); + editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor + }), + }) + } +} + +impl Render for AutoHeightEditorStory { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + div() + .size_full() + .bg(white()) + .text_sm() + .child(div().w_32().bg(gpui::black()).child(self.editor.clone())) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 0354097c0b..4fe76ce878 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -12,6 +12,7 @@ use ui::prelude::*; #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] #[strum(serialize_all = "snake_case")] pub enum ComponentStory { + AutoHeightEditor, Avatar, Button, Checkbox, @@ -23,6 +24,7 @@ pub enum ComponentStory { Keybinding, Label, List, + ListHeader, ListItem, Scroll, Text, @@ -33,6 +35,7 @@ pub enum ComponentStory { impl ComponentStory { pub fn story(&self, cx: &mut WindowContext) -> AnyView { match self { + Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(), Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(), Self::Button => cx.build_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(), @@ -44,6 +47,7 @@ impl ComponentStory { Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(), Self::Label => cx.build_view(|_| ui::LabelStory).into(), Self::List => cx.build_view(|_| ui::ListStory).into(), + Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(), Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(), Self::Scroll => ScrollStory::view(cx).into(), Self::Text => TextStory::view(cx).into(), diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs index 7b0a0c3d3a..be55194e76 100644 --- a/crates/theme_selector2/src/theme_selector.rs +++ b/crates/theme_selector2/src/theme_selector.rs @@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt; use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ - actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, - SharedString, View, ViewContext, VisualContext, WeakView, + actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View, + ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; -use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings}; -use ui::ListItem; +use theme::{Theme, ThemeRegistry, ThemeSettings}; +use ui::{prelude::*, ListItem}; use util::ResultExt; use workspace::{ui::HighlightedLabel, Workspace}; diff --git a/crates/ui2/src/components/button/mod.rs b/crates/ui2/src/components/button.rs similarity index 100% rename from crates/ui2/src/components/button/mod.rs rename to crates/ui2/src/components/button.rs diff --git a/crates/ui2/src/components/button/button.rs b/crates/ui2/src/components/button/button.rs index b4e666e9ad..ce26ee76a5 100644 --- a/crates/ui2/src/components/button/button.rs +++ b/crates/ui2/src/components/button/button.rs @@ -1,7 +1,7 @@ use gpui::AnyView; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle}; #[derive(IntoElement)] pub struct Button { @@ -54,12 +54,12 @@ impl ButtonCommon for Button { self.base.id() } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } @@ -79,7 +79,7 @@ impl RenderOnce for Button { } else if self.base.selected { Color::Selected } else { - Color::Default + self.label_color.unwrap_or_default() }; self.base.child( diff --git a/crates/ui2/src/components/button/button_like.rs b/crates/ui2/src/components/button/button_like.rs index f1131f9b22..89725a9821 100644 --- a/crates/ui2/src/components/button/button_like.rs +++ b/crates/ui2/src/components/button/button_like.rs @@ -1,4 +1,4 @@ -use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful}; +use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful}; use smallvec::SmallVec; use crate::h_stack; @@ -6,13 +6,13 @@ use crate::prelude::*; pub trait ButtonCommon: Clickable + Disableable { fn id(&self) -> &ElementId; - fn style(self, style: ButtonStyle2) -> Self; - fn size(self, size: ButtonSize2) -> Self; + fn style(self, style: ButtonStyle) -> Self; + fn size(self, size: ButtonSize) -> Self; fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self; } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] -pub enum ButtonStyle2 { +pub enum ButtonStyle { #[default] Filled, // Tinted, @@ -21,54 +21,57 @@ pub enum ButtonStyle2 { } #[derive(Debug, Clone)] -pub struct ButtonStyle { +pub(crate) struct ButtonLikeStyles { pub background: Hsla, + #[allow(unused)] pub border_color: Hsla, + #[allow(unused)] pub label_color: Hsla, + #[allow(unused)] pub icon_color: Hsla, } -impl ButtonStyle2 { - pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle { +impl ButtonStyle { + pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_background, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), - border_color: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, } } - pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_hover, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_hover, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), - border_color: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), // TODO: These are not great label_color: Color::Muted.color(cx), // TODO: These are not great @@ -77,23 +80,23 @@ impl ButtonStyle2 { } } - pub fn active(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_active, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_active, - border_color: gpui::transparent_black(), + border_color: transparent_black(), label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), - border_color: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), // TODO: These are not great label_color: Color::Muted.color(cx), // TODO: These are not great @@ -102,22 +105,23 @@ impl ButtonStyle2 { } } - pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle { + #[allow(unused)] + pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_background, border_color: cx.theme().colors().border_focused, label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, border_color: cx.theme().colors().border_focused, label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), border_color: cx.theme().colors().border_focused, label_color: Color::Accent.color(cx), icon_color: Color::Accent.color(cx), @@ -125,23 +129,23 @@ impl ButtonStyle2 { } } - pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle { + pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { - ButtonStyle2::Filled => ButtonStyle { + ButtonStyle::Filled => ButtonLikeStyles { background: cx.theme().colors().element_disabled, border_color: cx.theme().colors().border_disabled, label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, - ButtonStyle2::Subtle => ButtonStyle { + ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_disabled, border_color: cx.theme().colors().border_disabled, label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, - ButtonStyle2::Transparent => ButtonStyle { - background: gpui::transparent_black(), - border_color: gpui::transparent_black(), + ButtonStyle::Transparent => ButtonLikeStyles { + background: transparent_black(), + border_color: transparent_black(), label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, @@ -150,19 +154,19 @@ impl ButtonStyle2 { } #[derive(Default, PartialEq, Clone, Copy)] -pub enum ButtonSize2 { +pub enum ButtonSize { #[default] Default, Compact, None, } -impl ButtonSize2 { +impl ButtonSize { fn height(self) -> Rems { match self { - ButtonSize2::Default => rems(22. / 16.), - ButtonSize2::Compact => rems(18. / 16.), - ButtonSize2::None => rems(16. / 16.), + ButtonSize::Default => rems(22. / 16.), + ButtonSize::Compact => rems(18. / 16.), + ButtonSize::None => rems(16. / 16.), } } } @@ -170,10 +174,10 @@ impl ButtonSize2 { #[derive(IntoElement)] pub struct ButtonLike { id: ElementId, - pub(super) style: ButtonStyle2, + pub(super) style: ButtonStyle, pub(super) disabled: bool, pub(super) selected: bool, - size: ButtonSize2, + size: ButtonSize, tooltip: Option AnyView>>, on_click: Option>, children: SmallVec<[AnyElement; 2]>, @@ -183,10 +187,10 @@ impl ButtonLike { pub fn new(id: impl Into) -> Self { Self { id: id.into(), - style: ButtonStyle2::default(), + style: ButtonStyle::default(), disabled: false, selected: false, - size: ButtonSize2::Default, + size: ButtonSize::Default, tooltip: None, children: SmallVec::new(), on_click: None, @@ -220,12 +224,12 @@ impl ButtonCommon for ButtonLike { &self.id } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.style = style; self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.size = size; self } diff --git a/crates/ui2/src/components/button/icon_button.rs b/crates/ui2/src/components/button/icon_button.rs index 7746c3f8be..a62832059d 100644 --- a/crates/ui2/src/components/button/icon_button.rs +++ b/crates/ui2/src/components/button/icon_button.rs @@ -1,7 +1,7 @@ use gpui::{Action, AnyView}; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize}; #[derive(IntoElement)] pub struct IconButton { @@ -65,12 +65,12 @@ impl ButtonCommon for IconButton { self.base.id() } - fn style(mut self, style: ButtonStyle2) -> Self { + fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - fn size(mut self, size: ButtonSize2) -> Self { + fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index aafd045391..88650b6ae8 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,73 +1,11 @@ +mod list; mod list_header; mod list_item; mod list_separator; mod list_sub_header; -use gpui::{AnyElement, Div}; -use smallvec::SmallVec; - -use crate::prelude::*; -use crate::{v_stack, Label}; - +pub use list::*; pub use list_header::*; pub use list_item::*; pub use list_separator::*; pub use list_sub_header::*; - -#[derive(IntoElement)] -pub struct List { - /// Message to display when the list is empty - /// Defaults to "No items" - empty_message: SharedString, - header: Option, - toggle: Option, - children: SmallVec<[AnyElement; 2]>, -} - -impl List { - pub fn new() -> Self { - Self { - empty_message: "No items".into(), - header: None, - toggle: None, - children: SmallVec::new(), - } - } - - pub fn empty_message(mut self, empty_message: impl Into) -> Self { - self.empty_message = empty_message.into(); - self - } - - pub fn header(mut self, header: ListHeader) -> Self { - self.header = Some(header); - self - } - - pub fn toggle(mut self, toggle: impl Into>) -> Self { - self.toggle = toggle.into(); - self - } -} - -impl ParentElement for List { - fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { - &mut self.children - } -} - -impl RenderOnce for List { - type Rendered = Div; - - fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - v_stack() - .w_full() - .py_1() - .children(self.header.map(|header| header)) - .map(|this| match (self.children.is_empty(), self.toggle) { - (false, _) => this.children(self.children), - (true, Some(false)) => this, - (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)), - }) - } -} diff --git a/crates/ui2/src/components/list/list.rs b/crates/ui2/src/components/list/list.rs new file mode 100644 index 0000000000..fdfe256bd6 --- /dev/null +++ b/crates/ui2/src/components/list/list.rs @@ -0,0 +1,60 @@ +use gpui::{AnyElement, Div}; +use smallvec::SmallVec; + +use crate::{prelude::*, v_stack, Label, ListHeader}; + +#[derive(IntoElement)] +pub struct List { + /// Message to display when the list is empty + /// Defaults to "No items" + empty_message: SharedString, + header: Option, + toggle: Option, + children: SmallVec<[AnyElement; 2]>, +} + +impl List { + pub fn new() -> Self { + Self { + empty_message: "No items".into(), + header: None, + toggle: None, + children: SmallVec::new(), + } + } + + pub fn empty_message(mut self, empty_message: impl Into) -> Self { + self.empty_message = empty_message.into(); + self + } + + pub fn header(mut self, header: impl Into>) -> Self { + self.header = header.into(); + self + } + + pub fn toggle(mut self, toggle: impl Into>) -> Self { + self.toggle = toggle.into(); + self + } +} + +impl ParentElement for List { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for List { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + v_stack().w_full().py_1().children(self.header).map(|this| { + match (self.children.is_empty(), self.toggle) { + (false, _) => this.children(self.children), + (true, Some(false)) => this, + (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)), + } + }) + } +} diff --git a/crates/ui2/src/components/list/list_header.rs b/crates/ui2/src/components/list/list_header.rs index 8761acd608..799b1c5dae 100644 --- a/crates/ui2/src/components/list/list_header.rs +++ b/crates/ui2/src/components/list/list_header.rs @@ -1,22 +1,16 @@ use std::rc::Rc; -use gpui::{ClickEvent, Div}; +use gpui::{AnyElement, ClickEvent, Div}; +use smallvec::SmallVec; use crate::prelude::*; -use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label}; - -pub enum ListHeaderMeta { - Tools(Vec), - // TODO: This should be a button - Button(Label), - Text(Label), -} +use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label}; #[derive(IntoElement)] pub struct ListHeader { label: SharedString, left_icon: Option, - meta: Option, + meta: SmallVec<[AnyElement; 2]>, toggle: Option, on_toggle: Option>, inset: bool, @@ -28,7 +22,7 @@ impl ListHeader { Self { label: label.into(), left_icon: None, - meta: None, + meta: SmallVec::new(), inset: false, toggle: None, on_toggle: None, @@ -49,21 +43,19 @@ impl ListHeader { self } - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; + pub fn left_icon(mut self, left_icon: impl Into>) -> Self { + self.left_icon = left_icon.into(); self } - pub fn right_button(self, button: IconButton) -> Self { - self.meta(Some(ListHeaderMeta::Tools(vec![button]))) - } - - pub fn meta(mut self, meta: Option) -> Self { - self.meta = meta; + pub fn meta(mut self, meta: impl IntoElement) -> Self { + self.meta.push(meta.into_any_element()); self } +} - pub fn selected(mut self, selected: bool) -> Self { +impl Selectable for ListHeader { + fn selected(mut self, selected: bool) -> Self { self.selected = selected; self } @@ -73,18 +65,6 @@ impl RenderOnce for ListHeader { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - let meta = match self.meta { - Some(ListHeaderMeta::Tools(icons)) => div().child( - h_stack() - .gap_2() - .items_center() - .children(icons.into_iter().map(|i| i.icon_color(Color::Muted))), - ), - Some(ListHeaderMeta::Button(label)) => div().child(label), - Some(ListHeaderMeta::Text(label)) => div().child(label), - None => div(), - }; - h_stack().w_full().relative().child( div() .h_5() @@ -118,7 +98,7 @@ impl RenderOnce for ListHeader { .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)), ), ) - .child(meta), + .child(h_stack().gap_2().items_center().children(self.meta)), ) } } diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index 7ad1d5fb72..85198416cd 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -83,11 +83,6 @@ impl ListItem { self } - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - pub fn left_child(mut self, left_content: impl IntoElement) -> Self { self.left_slot = Some(left_content.into_any_element()); self @@ -109,6 +104,13 @@ impl ListItem { } } +impl Selectable for ListItem { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + impl ParentElement for ListItem { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index e870515caf..113c2679b7 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -8,6 +8,7 @@ mod icon_button; mod keybinding; mod label; mod list; +mod list_header; mod list_item; pub use avatar::*; @@ -20,4 +21,5 @@ pub use icon_button::*; pub use keybinding::*; pub use label::*; pub use list::*; +pub use list_header::*; pub use list_item::*; diff --git a/crates/ui2/src/components/stories/button.rs b/crates/ui2/src/components/stories/button.rs index 918c321c25..17bcd8b268 100644 --- a/crates/ui2/src/components/stories/button.rs +++ b/crates/ui2/src/components/stories/button.rs @@ -2,7 +2,7 @@ use gpui::{Div, Render}; use story::Story; use crate::prelude::*; -use crate::{Button, ButtonStyle2}; +use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -14,9 +14,13 @@ impl Render for ButtonStory { .child(Story::title_for::