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    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("Find All References", Box::new(FindAllReferences))
 52                .action(
 53                    "Code Actions",
 54                    Box::new(ToggleCodeActions {
 55                        deployed_from_indicator: false,
 56                    }),
 57                )
 58                .separator()
 59                .action("Reveal in Finder", Box::new(RevealInFinder))
 60        })
 61    };
 62    let context_menu_focus = context_menu.focus_handle(cx);
 63    cx.focus(&context_menu_focus);
 64
 65    let _subscription = cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
 66        this.mouse_context_menu.take();
 67        if context_menu_focus.contains_focused(cx) {
 68            this.focus(cx);
 69        }
 70    });
 71
 72    editor.mouse_context_menu = Some(MouseContextMenu {
 73        position,
 74        context_menu,
 75        _subscription,
 76    });
 77    cx.notify();
 78}
 79
 80#[cfg(test)]
 81mod tests {
 82    use super::*;
 83    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
 84    use indoc::indoc;
 85
 86    #[gpui::test]
 87    async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
 88        init_test(cx, |_| {});
 89
 90        let mut cx = EditorLspTestContext::new_rust(
 91            lsp::ServerCapabilities {
 92                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 93                ..Default::default()
 94            },
 95            cx,
 96        )
 97        .await;
 98
 99        cx.set_state(indoc! {"
100            fn teˇst() {
101                do_work();
102            }
103        "});
104        let point = cx.display_point(indoc! {"
105            fn test() {
106                do_wˇork();
107            }
108        "});
109        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_none()));
110        cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
111
112        cx.assert_editor_state(indoc! {"
113            fn test() {
114                do_wˇork();
115            }
116        "});
117        cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_some()));
118    }
119}