mouse_context_menu.rs

  1use crate::{
  2    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
  3    Rename, RevealInFinder, SelectMode, ToggleCodeActions,
  4};
  5use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
  6
  7pub struct MouseContextMenu {
  8    pub(crate) position: Point<Pixels>,
  9    pub(crate) context_menu: View<ui::ContextMenu>,
 10    _subscription: Subscription,
 11}
 12
 13pub fn deploy_context_menu(
 14    editor: &mut Editor,
 15    position: Point<Pixels>,
 16    point: DisplayPoint,
 17    cx: &mut ViewContext<Editor>,
 18) {
 19    if !editor.is_focused(cx) {
 20        editor.focus(cx);
 21    }
 22
 23    // Don't show context menu for inline editors
 24    if editor.mode() != EditorMode::Full {
 25        return;
 26    }
 27
 28    // Don't show the context menu if there isn't a project associated with this editor
 29    if editor.project.is_none() {
 30        return;
 31    }
 32
 33    // Move the cursor to the clicked location so that dispatched actions make sense
 34    editor.change_selections(None, cx, |s| {
 35        s.clear_disjoint();
 36        s.set_pending_display_range(point..point, SelectMode::Character);
 37    });
 38
 39    let context_menu = ui::ContextMenu::build(cx, |menu, _cx| {
 40        menu.action("Rename Symbol", Box::new(Rename))
 41            .action("Go to Definition", Box::new(GoToDefinition))
 42            .action("Go to Type Definition", Box::new(GoToTypeDefinition))
 43            .action("Find All References", Box::new(FindAllReferences))
 44            .action(
 45                "Code Actions",
 46                Box::new(ToggleCodeActions {
 47                    deployed_from_indicator: false,
 48                }),
 49            )
 50            .separator()
 51            .action("Reveal in Finder", Box::new(RevealInFinder))
 52    });
 53    let context_menu_focus = context_menu.focus_handle(cx);
 54    cx.focus(&context_menu_focus);
 55
 56    let _subscription = cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
 57        this.mouse_context_menu.take();
 58        if context_menu_focus.contains_focused(cx) {
 59            this.focus(cx);
 60        }
 61    });
 62
 63    editor.mouse_context_menu = Some(MouseContextMenu {
 64        position,
 65        context_menu,
 66        _subscription,
 67    });
 68    cx.notify();
 69}
 70
 71#[cfg(test)]
 72mod tests {
 73    use super::*;
 74    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
 75    use indoc::indoc;
 76
 77    #[gpui::test]
 78    async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
 79        init_test(cx, |_| {});
 80
 81        let mut cx = EditorLspTestContext::new_rust(
 82            lsp::ServerCapabilities {
 83                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 84                ..Default::default()
 85            },
 86            cx,
 87        )
 88        .await;
 89
 90        cx.set_state(indoc! {"
 91            fn teˇst() {
 92                do_work();
 93            }
 94        "});
 95        let point = cx.display_point(indoc! {"
 96            fn test() {
 97                do_wˇork();
 98            }
 99        "});
100        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none()));
101        cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
102
103        cx.assert_editor_state(indoc! {"
104            fn test() {
105                do_wˇork();
106            }
107        "});
108        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_some()));
109    }
110}