From d796b543e0f2a8255e46b7862396f790f51317a2 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Mon, 11 Jul 2022 14:14:33 -0700 Subject: [PATCH 1/3] WIP add basic context menu and make progress toward adding quick actions to it --- Cargo.lock | 1 + crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 14 +++-- crates/editor/src/element.rs | 24 +++++++++ crates/editor/src/mouse_context_menu.rs | 61 ++++++++++++++++++++++ crates/editor/src/selections_collection.rs | 38 +++++++++++++- 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 crates/editor/src/mouse_context_menu.rs diff --git a/Cargo.lock b/Cargo.lock index 6c87bb055f7fcbbbffaa3cbd0f5daa818d32f038..14016ae243dfb89addad7b9709fc6eac844c9d9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,6 +1611,7 @@ dependencies = [ "anyhow", "clock", "collections", + "context_menu", "ctor", "env_logger", "futures", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 6e1e3b09bd9b56f228b8a9c8524de5fc9d7e6fe3..dfd4938742d3a365d477c01c8d54e2ce9bdb2e9b 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -23,6 +23,7 @@ test-support = [ text = { path = "../text" } clock = { path = "../clock" } collections = { path = "../collections" } +context_menu = { path = "../context_menu" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } language = { path = "../language" } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e58f1fc341c8404e33d0dab583de934a0bf205e5..baa346f7f0d9da88ab69efc0459c08a78f9434d4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4,6 +4,7 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; +mod mouse_context_menu; pub mod movement; mod multi_buffer; pub mod selections_collection; @@ -319,6 +320,7 @@ pub fn init(cx: &mut MutableAppContext) { hover_popover::init(cx); link_go_to_definition::init(cx); + mouse_context_menu::init(cx); workspace::register_project_item::(cx); workspace::register_followable_item::(cx); @@ -425,6 +427,7 @@ pub struct Editor { background_highlights: BTreeMap Color, Vec>)>, nav_history: Option, context_menu: Option, + mouse_context_menu: ViewHandle, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, available_code_actions: Option<(ModelHandle, Arc<[CodeAction]>)>, @@ -1010,11 +1013,11 @@ impl Editor { background_highlights: Default::default(), nav_history: None, context_menu: None, + mouse_context_menu: cx.add_view(|cx| context_menu::ContextMenu::new(cx)), completion_tasks: Default::default(), next_completion_id: 0, available_code_actions: Default::default(), code_actions_task: Default::default(), - document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, @@ -1596,7 +1599,7 @@ impl Editor { s.delete(newest_selection.id) } - s.set_pending_range(start..end, mode); + s.set_pending_anchor_range(start..end, mode); }); } @@ -5780,7 +5783,12 @@ impl View for Editor { }); } - EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed() + Stack::new() + .with_child( + EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed(), + ) + .with_child(ChildView::new(&self.mouse_context_menu).boxed()) + .boxed() } fn ui_name() -> &'static str { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 56f664566ecd03bba53cbd7960e3ac5be8d33287..99d60ed9a279a581e9973a1c888486992a46af21 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7,6 +7,7 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, hover_popover::HoverAt, link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink}, + mouse_context_menu::DeployMouseContextMenu, EditorStyle, }; use clock::ReplicaId; @@ -152,6 +153,24 @@ impl EditorElement { true } + fn mouse_right_down( + &self, + position: Vector2F, + layout: &mut LayoutState, + paint: &mut PaintState, + cx: &mut EventContext, + ) -> bool { + if !paint.text_bounds.contains_point(position) { + return false; + } + + let snapshot = self.snapshot(cx.app); + let (point, _) = paint.point_for_position(&snapshot, layout, position); + + cx.dispatch_action(DeployMouseContextMenu { position, point }); + true + } + fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool { if self.view(cx.app.as_ref()).is_selecting() { cx.dispatch_action(Select(SelectPhase::End)); @@ -1482,6 +1501,11 @@ impl Element for EditorElement { paint, cx, ), + Event::MouseDown(MouseEvent { + button: MouseButton::Right, + position, + .. + }) => self.mouse_right_down(*position, layout, paint, cx), Event::MouseUp(MouseEvent { button: MouseButton::Left, position, diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..1313ad346fd751cf8d80d11add4e5cbc89d5de0f --- /dev/null +++ b/crates/editor/src/mouse_context_menu.rs @@ -0,0 +1,61 @@ +use context_menu::{ContextMenu, ContextMenuItem}; +use gpui::{ + geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, Task, ViewContext, + ViewHandle, +}; + +use crate::{ + DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, Rename, SelectMode, +}; + +#[derive(Clone, PartialEq)] +pub struct DeployMouseContextMenu { + pub position: Vector2F, + pub point: DisplayPoint, +} + +impl_internal_actions!(editor, [DeployMouseContextMenu]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(deploy_context_menu); +} + +pub struct MouseContextMenuState { + pub context_menu: ViewHandle, + pub task: Option>, +} + +pub fn deploy_context_menu( + editor: &mut Editor, + &DeployMouseContextMenu { position, point }: &DeployMouseContextMenu, + cx: &mut ViewContext, +) { + // 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; + } + + // 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); + }); + + editor.mouse_context_menu.update(cx, |menu, cx| { + menu.show( + position, + vec![ + ContextMenuItem::item("Rename Symbol", Rename), + ContextMenuItem::item("Go To Definition", GoToDefinition), + ContextMenuItem::item("Find All References", FindAllReferences), + ], + cx, + ); + }); + cx.notify(); +} diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 026144db647fbc6a0692660eb4e481b8da4538c1..a05a46fc6d363ec3b92f51120cf283e9788ddbcf 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -384,7 +384,7 @@ impl<'a> MutableSelectionsCollection<'a> { } } - pub fn set_pending_range(&mut self, range: Range, mode: SelectMode) { + pub fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection: Selection { id: post_inc(&mut self.collection.next_selection_id), @@ -398,6 +398,42 @@ impl<'a> MutableSelectionsCollection<'a> { self.selections_changed = true; } + pub fn set_pending_display_range(&mut self, range: Range, mode: SelectMode) { + let (start, end, reversed) = { + let display_map = self.display_map(); + let buffer = self.buffer(); + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + + let end_bias = if end > start { Bias::Left } else { Bias::Right }; + ( + buffer.anchor_before(start.to_point(&display_map)), + buffer.anchor_at(end.to_point(&display_map), end_bias), + reversed, + ) + }; + + let new_pending = PendingSelection { + selection: Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + }, + mode, + }; + + self.collection.pending = Some(new_pending); + self.selections_changed = true; + } + pub fn set_pending(&mut self, selection: Selection, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection, mode }); self.selections_changed = true; From b850e41d6f0515b9f7bc12ed6d6a30222dde1b08 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Jul 2022 13:34:23 -0700 Subject: [PATCH 2/3] Add editor mouse context menu with some basic refactorings and an entry to pop the code actions --- crates/editor/src/mouse_context_menu.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 1313ad346fd751cf8d80d11add4e5cbc89d5de0f..be083b2bce2e463685b12bf9c0cd55e5d43ab4ba 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,11 +1,9 @@ -use context_menu::{ContextMenu, ContextMenuItem}; -use gpui::{ - geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, Task, ViewContext, - ViewHandle, -}; +use context_menu::ContextMenuItem; +use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext}; use crate::{ DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, Rename, SelectMode, + ToggleCodeActions, }; #[derive(Clone, PartialEq)] @@ -20,11 +18,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(deploy_context_menu); } -pub struct MouseContextMenuState { - pub context_menu: ViewHandle, - pub task: Option>, -} - pub fn deploy_context_menu( editor: &mut Editor, &DeployMouseContextMenu { position, point }: &DeployMouseContextMenu, @@ -53,6 +46,12 @@ pub fn deploy_context_menu( ContextMenuItem::item("Rename Symbol", Rename), ContextMenuItem::item("Go To Definition", GoToDefinition), ContextMenuItem::item("Find All References", FindAllReferences), + ContextMenuItem::item( + "Code Actions", + ToggleCodeActions { + deployed_from_indicator: false, + }, + ), ], cx, ); From 5366ed4404364d1612782acd52ae12b881ee9abe Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Jul 2022 15:35:01 -0700 Subject: [PATCH 3/3] Add basic test for editor context menu --- crates/context_menu/src/context_menu.rs | 4 +++ crates/editor/src/mouse_context_menu.rs | 43 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 85a6cd1e19cbc851c1e4ad71d1216460c5b334e9..39477bc927b742d90de3c0e23b53c177d36faf25 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -124,6 +124,10 @@ impl ContextMenu { } } + pub fn visible(&self) -> bool { + self.visible + } + fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext) { if let Some(ix) = self .items diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index be083b2bce2e463685b12bf9c0cd55e5d43ab4ba..f30bc0a6788ca72fbab2f138bf7665344b333b9b 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -58,3 +58,46 @@ pub fn deploy_context_menu( }); cx.notify(); } + +#[cfg(test)] +mod tests { + use indoc::indoc; + + use crate::test::EditorLspTestContext; + + use super::*; + + #[gpui::test] + async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + fn te|st() + do_work();"}); + let point = cx.display_point(indoc! {" + fn test() + do_w|ork();"}); + cx.update_editor(|editor, cx| { + deploy_context_menu( + editor, + &DeployMouseContextMenu { + position: Default::default(), + point, + }, + cx, + ) + }); + + cx.assert_editor_state(indoc! {" + fn test() + do_w|ork();"}); + cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible())); + } +}