mouse_context_menu.rs

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