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;