Files
oak-gpui/crates/editor/src/mouse_context_menu.rs
T
Vitaly Slobodin f2e7c635ac editor: Add Cut, Copy, and Paste actions to the context menu (#11878)
Hi, I saw someone on Twitter mentioned that missing Cut, Copy and Paste
actions in the context menu in the editor block them from using Zed. It
turns out that resolving this issue is simply a matter of adding these
actions to the mouse context menu. To keep items in the context menu
grouped, I placed them at the top of the menu with a separator at the
end. Let me know if that's OK. Thanks!

Here is the screenshot:

![CleanShot 2024-05-16 at 07 04
44@2x](https://github.com/zed-industries/zed/assets/1894248/2ac84001-cdd7-4c01-b597-c5b1dc3e7fa3)

Release Notes:

- Added "Cut", "Copy", and "Paste" actions to the context menu
([#4280](https://github.com/zed-industries/zed/issues/4280)).
2024-05-16 08:59:17 -04:00

146 lines
4.6 KiB
Rust

use crate::{
Copy, Cut, DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition,
GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFinder, SelectMode,
ToggleCodeActions,
};
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
use workspace::OpenInTerminal;
pub struct MouseContextMenu {
pub(crate) position: Point<Pixels>,
pub(crate) context_menu: View<ui::ContextMenu>,
_subscription: Subscription,
}
impl MouseContextMenu {
pub(crate) fn new(
position: Point<Pixels>,
context_menu: View<ui::ContextMenu>,
cx: &mut ViewContext<Editor>,
) -> Self {
let context_menu_focus = context_menu.focus_handle(cx);
cx.focus(&context_menu_focus);
let _subscription =
cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
this.mouse_context_menu.take();
if context_menu_focus.contains_focused(cx) {
this.focus(cx);
}
});
Self {
position,
context_menu,
_subscription,
}
}
}
pub fn deploy_context_menu(
editor: &mut Editor,
position: Point<Pixels>,
point: DisplayPoint,
cx: &mut ViewContext<Editor>,
) {
if !editor.is_focused(cx) {
editor.focus(cx);
}
// Don't show context menu for inline editors
if editor.mode() != EditorMode::Full {
return;
}
let context_menu = if let Some(custom) = editor.custom_context_menu.take() {
let menu = custom(editor, point, cx);
editor.custom_context_menu = Some(custom);
if menu.is_none() {
return;
}
menu.unwrap()
} else {
// Don't show the context menu if there isn't a project associated with this editor
if editor.project.is_none() {
return;
}
// Move the cursor to the clicked location so that dispatched actions make sense
editor.change_selections(None, cx, |s| {
s.clear_disjoint();
s.set_pending_display_range(point..point, SelectMode::Character);
});
let focus = cx.focused();
ui::ContextMenu::build(cx, |menu, _cx| {
let builder = menu
.action("Rename Symbol", Box::new(Rename))
.action("Go to Definition", Box::new(GoToDefinition))
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
.action("Go to Implementation", Box::new(GoToImplementation))
.action("Find All References", Box::new(FindAllReferences))
.action(
"Code Actions",
Box::new(ToggleCodeActions {
deployed_from_indicator: None,
}),
)
.separator()
.action("Cut", Box::new(Cut))
.action("Copy", Box::new(Copy))
.action("Paste", Box::new(Paste))
.separator()
.action("Reveal in Finder", Box::new(RevealInFinder))
.action("Open in Terminal", Box::new(OpenInTerminal));
match focus {
Some(focus) => builder.context(focus),
None => builder,
}
})
};
let mouse_context_menu = MouseContextMenu::new(position, context_menu, cx);
editor.mouse_context_menu = Some(mouse_context_menu);
cx.notify();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
use indoc::indoc;
#[gpui::test]
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
fn teˇst() {
do_work();
}
"});
let point = cx.display_point(indoc! {"
fn test() {
do_wˇork();
}
"});
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.assert_editor_state(indoc! {"
fn test() {
do_wˇork();
}
"});
cx.editor(|editor, _app| assert!(editor.mouse_context_menu.is_some()));
}
}