use gpui::prelude::FluentBuilder as _; use gpui::{ Anchor, App, AppContext as _, Context, DismissEvent, Entity, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Point, Render, Styled, Subscription, Window, anchored, deferred, div, px, }; use crate::input::popovers::ContextMenu; use crate::input::{self, InputState}; use crate::menu::PopupMenu; /// Context menu for mouse right clicks. pub(crate) struct InputContextMenu { editor: Entity, menu: Entity, mouse_position: Point, open: bool, _subscriptions: Vec, } impl InputState { pub(crate) fn handle_right_click_menu( &mut self, event: &MouseDownEvent, offset: usize, window: &mut Window, cx: &mut Context, ) { // Show Mouse context menu if !self.selected_range.contains(offset) { self.move_to(offset, None, cx); } self.context_menu_content = Some(ContextMenu::RightClick(self.context_menu.clone())); let is_code_editor = self.mode.is_code_editor(); if is_code_editor { self.handle_hover_definition(offset, window, cx); } let is_enable = !self.disabled; let has_goto_definition = is_enable && self.lsp.definition_provider.is_some(); let has_code_action = is_enable && !self.lsp.code_action_providers.is_empty(); let is_selected = !self.selected_range.is_empty(); let has_paste = is_enable && cx.read_from_clipboard().is_some(); let action_context = self.focus_handle.clone(); self.context_menu.update(cx, |this, cx| { this.mouse_position = event.position; this.menu.update(cx, |menu, cx| { let new_menu = if let Some(builder) = &self.context_menu_builder { builder(PopupMenu::new(cx), window, cx) } else { PopupMenu::new(cx) .when(is_code_editor, |m| { m.menu_with_enable( "Go to Definition", Box::new(input::GoToDefinition), has_goto_definition, ) .menu_with_enable( "Show Code Actions", Box::new(input::ToggleCodeActions), has_code_action, ) .separator() }) .menu_with_enable("Cut", Box::new(input::Cut), is_enable && is_selected) .menu_with_enable("Copy", Box::new(input::Copy), is_selected) .menu_with_enable("Paste", Box::new(input::Paste), has_paste) .separator() .menu("Select All", Box::new(input::SelectAll)) }; menu.menu_items = new_menu.menu_items; menu.action_context = Some(action_context); cx.notify(); }); cx.defer_in(window, |this, _, cx| { this.open = true; cx.notify(); }); }); } } impl InputContextMenu { pub(crate) fn new( editor: Entity, window: &mut Window, cx: &mut App, ) -> Entity { cx.new(|cx| { let menu = cx.new(|cx| PopupMenu::new(cx).small()); let _subscriptions = vec![cx.subscribe_in(&menu, window, { move |this: &mut Self, _, _: &DismissEvent, window, cx| { this.close(window, cx); } })]; Self { editor, menu, mouse_position: Point::default(), open: false, _subscriptions, } }) } #[inline] pub(crate) fn is_open(&self) -> bool { self.open } #[inline] pub(crate) fn close(&mut self, window: &mut Window, cx: &mut Context) { self.open = false; self.editor.update(cx, |this, cx| { this.focus(window, cx); }); } } impl Render for InputContextMenu { fn render(&mut self, _: &mut Window, _cx: &mut Context) -> impl IntoElement { if !self.open { return div().into_any_element(); } deferred( anchored() .snap_to_window_with_margin(px(8.)) .anchor(Anchor::TopLeft) .position(self.mouse_position) .child(div().cursor_default().child(self.menu.clone())), ) .into_any_element() } }