Add action editor::OpenContextMenu (#21494)

This addresses the editor context menu portion of #17819.

Release Notes:

- Added `editor::OpenContextMenu` action to open context menu at current
cursor position.
This commit is contained in:
Michael Sloan 2024-12-04 14:13:50 -07:00 committed by GitHub
parent 0bde0f8e2f
commit f0fac41ca4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 60 deletions

View file

@ -108,7 +108,9 @@
"ctrl-'": "editor::ToggleHunkDiff", "ctrl-'": "editor::ToggleHunkDiff",
"ctrl-\"": "editor::ExpandAllHunkDiffs", "ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp", "ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame" "alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu"
} }
}, },
{ {

View file

@ -296,6 +296,7 @@ gpui::actions!(
NewlineBelow, NewlineBelow,
NextInlineCompletion, NextInlineCompletion,
NextScreen, NextScreen,
OpenContextMenu,
OpenExcerpts, OpenExcerpts,
OpenExcerptsSplit, OpenExcerptsSplit,
OpenProposedChangesEditor, OpenProposedChangesEditor,

View file

@ -13075,6 +13075,12 @@ impl Editor {
cx.write_to_clipboard(ClipboardItem::new_string(lines)); cx.write_to_clipboard(ClipboardItem::new_string(lines));
} }
pub fn open_context_menu(&mut self, _: &OpenContextMenu, cx: &mut ViewContext<Self>) {
self.request_autoscroll(Autoscroll::newest(), cx);
let position = self.selections.newest_display(cx).start;
mouse_context_menu::deploy_context_menu(self, None, position, cx);
}
pub fn inlay_hint_cache(&self) -> &InlayHintCache { pub fn inlay_hint_cache(&self) -> &InlayHintCache {
&self.inlay_hint_cache &self.inlay_hint_cache
} }
@ -13296,6 +13302,23 @@ impl Editor {
.get(&type_id) .get(&type_id)
.and_then(|item| item.to_any().downcast_ref::<T>()) .and_then(|item| item.to_any().downcast_ref::<T>())
} }
fn character_size(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;
let font_id = cx.text_system().resolve_font(&style.text.font());
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;
gpui::Point::new(em_width, line_height)
}
} }
fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize { fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
@ -14725,17 +14748,10 @@ impl ViewInputHandler for Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<gpui::Bounds<Pixels>> { ) -> Option<gpui::Bounds<Pixels>> {
let text_layout_details = self.text_layout_details(cx); let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style; let gpui::Point {
let font_id = cx.text_system().resolve_font(&style.text.font()); x: em_width,
let font_size = style.text.font_size.to_pixels(cx.rem_size()); y: line_height,
let line_height = style.text.line_height_in_pixels(cx.rem_size()); } = self.character_size(cx);
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')
.unwrap()
.size
.width;
let snapshot = self.snapshot(cx); let snapshot = self.snapshot(cx);
let scroll_position = snapshot.scroll_position(); let scroll_position = snapshot.scroll_position();

View file

@ -169,6 +169,7 @@ impl EditorElement {
crate::rust_analyzer_ext::apply_related_actions(view, cx); crate::rust_analyzer_ext::apply_related_actions(view, cx);
crate::clangd_ext::apply_related_actions(view, cx); crate::clangd_ext::apply_related_actions(view, cx);
register_action(view, cx, Editor::open_context_menu);
register_action(view, cx, Editor::move_left); register_action(view, cx, Editor::move_left);
register_action(view, cx, Editor::move_right); register_action(view, cx, Editor::move_right);
register_action(view, cx, Editor::move_down); register_action(view, cx, Editor::move_down);
@ -595,7 +596,7 @@ impl EditorElement {
position_map.point_for_position(text_hitbox.bounds, event.position); position_map.point_for_position(text_hitbox.bounds, event.position);
mouse_context_menu::deploy_context_menu( mouse_context_menu::deploy_context_menu(
editor, editor,
event.position, Some(event.position),
point_for_position.previous_valid, point_for_position.previous_valid,
cx, cx,
); );
@ -2730,6 +2731,7 @@ impl EditorElement {
&self, &self,
editor_snapshot: &EditorSnapshot, editor_snapshot: &EditorSnapshot,
visible_range: Range<DisplayRow>, visible_range: Range<DisplayRow>,
content_origin: gpui::Point<Pixels>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Option<AnyElement> { ) -> Option<AnyElement> {
let position = self.editor.update(cx, |editor, cx| { let position = self.editor.update(cx, |editor, cx| {
@ -2747,16 +2749,11 @@ impl EditorElement {
let mouse_context_menu = editor.mouse_context_menu.as_ref()?; let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
let (source_display_point, position) = match mouse_context_menu.position { let (source_display_point, position) = match mouse_context_menu.position {
MenuPosition::PinnedToScreen(point) => (None, point), MenuPosition::PinnedToScreen(point) => (None, point),
MenuPosition::PinnedToEditor { MenuPosition::PinnedToEditor { source, offset } => {
source,
offset_x,
offset_y,
} => {
let source_display_point = source.to_display_point(editor_snapshot); let source_display_point = source.to_display_point(editor_snapshot);
let mut source_point = editor.to_pixel_point(source, editor_snapshot, cx)?; let source_point = editor.to_pixel_point(source, editor_snapshot, cx)?;
source_point.x += offset_x; let position = content_origin + source_point + offset;
source_point.y += offset_y; (Some(source_display_point), position)
(Some(source_display_point), source_point)
} }
}; };
@ -4325,8 +4322,8 @@ fn deploy_blame_entry_context_menu(
}); });
editor.update(cx, move |editor, cx| { editor.update(cx, move |editor, cx| {
editor.mouse_context_menu = Some(MouseContextMenu::pinned_to_screen( editor.mouse_context_menu = Some(MouseContextMenu::new(
position, MenuPosition::PinnedToScreen(position),
context_menu, context_menu,
cx, cx,
)); ));
@ -5578,8 +5575,12 @@ impl Element for EditorElement {
); );
} }
let mouse_context_menu = let mouse_context_menu = self.layout_mouse_context_menu(
self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx); &snapshot,
start_row..end_row,
content_origin,
cx,
);
cx.with_element_namespace("crease_toggles", |cx| { cx.with_element_namespace("crease_toggles", |cx| {
self.prepaint_crease_toggles( self.prepaint_crease_toggles(

View file

@ -20,8 +20,7 @@ pub enum MenuPosition {
/// Disappears when the position is no longer visible. /// Disappears when the position is no longer visible.
PinnedToEditor { PinnedToEditor {
source: multi_buffer::Anchor, source: multi_buffer::Anchor,
offset_x: Pixels, offset: Point<Pixels>,
offset_y: Pixels,
}, },
} }
@ -48,36 +47,22 @@ impl MouseContextMenu {
context_menu: View<ui::ContextMenu>, context_menu: View<ui::ContextMenu>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<Self> { ) -> Option<Self> {
let context_menu_focus = context_menu.focus_handle(cx);
cx.focus(&context_menu_focus);
let _subscription = cx.subscribe(
&context_menu,
move |editor, _, _event: &DismissEvent, cx| {
editor.mouse_context_menu.take();
if context_menu_focus.contains_focused(cx) {
editor.focus(cx);
}
},
);
let editor_snapshot = editor.snapshot(cx); let editor_snapshot = editor.snapshot(cx);
let source_point = editor.to_pixel_point(source, &editor_snapshot, cx)?; let content_origin = editor.last_bounds?.origin
let offset = position - source_point; + Point {
x: editor.gutter_dimensions.width,
Some(Self { y: Pixels(0.0),
position: MenuPosition::PinnedToEditor { };
source, let source_position = editor.to_pixel_point(source, &editor_snapshot, cx)?;
offset_x: offset.x, let menu_position = MenuPosition::PinnedToEditor {
offset_y: offset.y, source,
}, offset: position - (source_position + content_origin),
context_menu, };
_subscription, return Some(MouseContextMenu::new(menu_position, context_menu, cx));
})
} }
pub(crate) fn pinned_to_screen( pub(crate) fn new(
position: Point<Pixels>, position: MenuPosition,
context_menu: View<ui::ContextMenu>, context_menu: View<ui::ContextMenu>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Self { ) -> Self {
@ -95,7 +80,7 @@ impl MouseContextMenu {
); );
Self { Self {
position: MenuPosition::PinnedToScreen(position), position,
context_menu, context_menu,
_subscription, _subscription,
} }
@ -119,7 +104,7 @@ fn display_ranges<'a>(
pub fn deploy_context_menu( pub fn deploy_context_menu(
editor: &mut Editor, editor: &mut Editor,
position: Point<Pixels>, position: Option<Point<Pixels>>,
point: DisplayPoint, point: DisplayPoint,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
@ -213,8 +198,18 @@ pub fn deploy_context_menu(
}) })
}; };
editor.mouse_context_menu = editor.mouse_context_menu = match position {
MouseContextMenu::pinned_to_editor(editor, source_anchor, position, context_menu, cx); Some(position) => {
MouseContextMenu::pinned_to_editor(editor, source_anchor, position, context_menu, cx)
}
None => {
let menu_position = MenuPosition::PinnedToEditor {
source: source_anchor,
offset: editor.character_size(cx),
};
Some(MouseContextMenu::new(menu_position, context_menu, cx))
}
};
cx.notify(); cx.notify();
} }
@ -248,7 +243,9 @@ mod tests {
} }
"}); "});
cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none())); cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none()));
cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx)); cx.update_editor(|editor, cx| {
deploy_context_menu(editor, Some(Default::default()), point, cx)
});
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn test() { fn test() {