diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 7a9c2cd97e..68b0d36a26 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -301,13 +301,19 @@ impl CollabTitlebarItem { .with_style(item_style.container) .into_any() })), - ContextMenuItem::item("Sign out", SignOut), - ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback), + ContextMenuItem::action("Sign out", SignOut), + ContextMenuItem::action( + "Send Feedback", + feedback::feedback_editor::GiveFeedback, + ), ] } else { vec![ - ContextMenuItem::item("Sign in", SignIn), - ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback), + ContextMenuItem::action("Sign in", SignIn), + ContextMenuItem::action( + "Send Feedback", + feedback::feedback_editor::GiveFeedback, + ), ] }; diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index fd5e1f1b09..7a7c55e6fd 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -10,7 +10,7 @@ use gpui::{ }; use menu::*; use settings::Settings; -use std::{any::TypeId, borrow::Cow, time::Duration}; +use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration}; #[derive(Copy, Clone, PartialEq)] struct Clicked; @@ -64,20 +64,44 @@ where } } +pub enum ContextMenuItemAction { + Action(Box), + Handler(Arc)>), +} + +impl Clone for ContextMenuItemAction { + fn clone(&self) -> Self { + match self { + Self::Action(action) => Self::Action(action.boxed_clone()), + Self::Handler(handler) => Self::Handler(handler.clone()), + } + } +} + pub enum ContextMenuItem { Item { label: ContextMenuItemLabel, - action: Box, + action: ContextMenuItemAction, }, Static(StaticItem), Separator, } impl ContextMenuItem { - pub fn item(label: impl Into, action: impl 'static + Action) -> Self { + pub fn action(label: impl Into, action: impl 'static + Action) -> Self { Self::Item { label: label.into(), - action: Box::new(action), + action: ContextMenuItemAction::Action(Box::new(action)), + } + } + + pub fn handler( + label: impl Into, + handler: impl 'static + Fn(&mut ViewContext), + ) -> Self { + Self::Item { + label: label.into(), + action: ContextMenuItemAction::Handler(Arc::new(handler)), } } @@ -91,7 +115,10 @@ impl ContextMenuItem { fn action_id(&self) -> Option { match self { - ContextMenuItem::Item { action, .. } => Some(action.id()), + ContextMenuItem::Item { action, .. } => match action { + ContextMenuItemAction::Action(action) => Some(action.id()), + ContextMenuItemAction::Handler(_) => None, + }, ContextMenuItem::Static(..) | ContextMenuItem::Separator => None, } } @@ -208,7 +235,17 @@ impl ContextMenu { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { if let Some(ix) = self.selected_index { if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) { - cx.dispatch_any_action(action.boxed_clone()); + match action { + ContextMenuItemAction::Action(action) => { + let window_id = cx.window_id(); + cx.dispatch_any_action_at( + window_id, + self.parent_view_id, + action.boxed_clone(), + ); + } + ContextMenuItemAction::Handler(handler) => handler(cx), + } self.reset(cx); } } @@ -351,13 +388,16 @@ impl ContextMenu { Some(ix) == self.selected_index, ); - KeystrokeLabel::new( - self.parent_view_id, - action.boxed_clone(), - style.keystroke.container, - style.keystroke.text.clone(), - ) - .into_any() + match action { + ContextMenuItemAction::Action(action) => KeystrokeLabel::new( + self.parent_view_id, + action.boxed_clone(), + style.keystroke.container, + style.keystroke.text.clone(), + ) + .into_any(), + ContextMenuItemAction::Handler(_) => Empty::new().into_any(), + } } ContextMenuItem::Static(_) => Empty::new().into_any(), @@ -389,11 +429,23 @@ impl ContextMenu { .with_children(self.items.iter().enumerate().map(|(ix, item)| { match item { ContextMenuItem::Item { label, action } => { - let action = action.boxed_clone(); + let action = action.clone(); let view_id = self.parent_view_id; MouseEventHandler::::new(ix, cx, |state, _| { let style = style.item.style_for(state, Some(ix) == self.selected_index); + let keystroke = match &action { + ContextMenuItemAction::Action(action) => Some( + KeystrokeLabel::new( + view_id, + action.boxed_clone(), + style.keystroke.container, + style.keystroke.text.clone(), + ) + .flex_float(), + ), + ContextMenuItemAction::Handler(_) => None, + }; Flex::row() .with_child(match label { @@ -406,15 +458,7 @@ impl ContextMenu { element(state, style) } }) - .with_child({ - KeystrokeLabel::new( - view_id, - action.boxed_clone(), - style.keystroke.container, - style.keystroke.text.clone(), - ) - .flex_float() - }) + .with_children(keystroke) .contained() .with_style(style.container) }) @@ -424,7 +468,16 @@ impl ContextMenu { .on_click(MouseButton::Left, move |_, _, cx| { let window_id = cx.window_id(); cx.dispatch_action(Clicked); - cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone()); + match &action { + ContextMenuItemAction::Action(action) => { + cx.dispatch_any_action_at( + window_id, + view_id, + action.boxed_clone(), + ); + } + ContextMenuItemAction::Handler(handler) => handler(cx), + } }) .on_drag(MouseButton::Left, |_, _, _| {}) .into_any() diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index a06e65db8d..df75dca51c 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -271,8 +271,8 @@ impl CopilotButton { ) { let mut menu_options = Vec::with_capacity(2); - menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn)); - menu_options.push(ContextMenuItem::item("Disable Copilot", HideCopilot)); + menu_options.push(ContextMenuItem::action("Sign In", InitiateSignIn)); + menu_options.push(ContextMenuItem::action("Disable Copilot", HideCopilot)); self.popup_menu.update(cx, |menu, cx| { menu.show( @@ -292,7 +292,7 @@ impl CopilotButton { if let Some(language) = &self.language { let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref())); - menu_options.push(ContextMenuItem::item( + menu_options.push(ContextMenuItem::action( format!( "{} Suggestions for {}", if language_enabled { "Hide" } else { "Show" }, @@ -305,7 +305,7 @@ impl CopilotButton { } let globally_enabled = cx.global::().show_copilot_suggestions(None); - menu_options.push(ContextMenuItem::item( + menu_options.push(ContextMenuItem::action( if globally_enabled { "Hide Suggestions for All Files" } else { @@ -317,7 +317,7 @@ impl CopilotButton { menu_options.push(ContextMenuItem::Separator); let icon_style = settings.theme.copilot.out_link_icon.clone(); - menu_options.push(ContextMenuItem::item( + menu_options.push(ContextMenuItem::action( move |state: &mut MouseState, style: &theme::ContextMenuItem| { Flex::row() .with_child(Label::new("Copilot Settings", style.label.clone())) @@ -328,7 +328,7 @@ impl CopilotButton { OsOpen::new(COPILOT_SETTINGS_URL), )); - menu_options.push(ContextMenuItem::item("Sign Out", SignOut)); + menu_options.push(ContextMenuItem::action("Sign Out", SignOut)); self.popup_menu.update(cx, |menu, cx| { menu.show( diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index a7a37dd5a6..f30bb3159a 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -51,18 +51,18 @@ pub fn deploy_context_menu( position, AnchorCorner::TopLeft, vec![ - ContextMenuItem::item("Rename Symbol", Rename), - ContextMenuItem::item("Go to Definition", GoToDefinition), - ContextMenuItem::item("Go to Type Definition", GoToTypeDefinition), - ContextMenuItem::item("Find All References", FindAllReferences), - ContextMenuItem::item( + ContextMenuItem::action("Rename Symbol", Rename), + ContextMenuItem::action("Go to Definition", GoToDefinition), + ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition), + ContextMenuItem::action("Find All References", FindAllReferences), + ContextMenuItem::action( "Code Actions", ToggleCodeActions { deployed_from_indicator: false, }, ), ContextMenuItem::Separator, - ContextMenuItem::item("Reveal in Finder", RevealInFinder), + ContextMenuItem::action("Reveal in Finder", RevealInFinder), ], cx, ); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c144ba569e..a8f3e4c44f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -296,38 +296,38 @@ impl ProjectPanel { if let Some((worktree, entry)) = self.selected_entry(cx) { let is_root = Some(entry) == worktree.root_entry(); if !project.is_remote() { - menu_entries.push(ContextMenuItem::item( + menu_entries.push(ContextMenuItem::action( "Add Folder to Project", workspace::AddFolderToProject, )); if is_root { - menu_entries.push(ContextMenuItem::item( + menu_entries.push(ContextMenuItem::action( "Remove from Project", workspace::RemoveWorktreeFromProject(worktree_id), )); } } - menu_entries.push(ContextMenuItem::item("New File", NewFile)); - menu_entries.push(ContextMenuItem::item("New Folder", NewDirectory)); + menu_entries.push(ContextMenuItem::action("New File", NewFile)); + menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory)); menu_entries.push(ContextMenuItem::Separator); - menu_entries.push(ContextMenuItem::item("Cut", Cut)); - menu_entries.push(ContextMenuItem::item("Copy", Copy)); + menu_entries.push(ContextMenuItem::action("Cut", Cut)); + menu_entries.push(ContextMenuItem::action("Copy", Copy)); menu_entries.push(ContextMenuItem::Separator); - menu_entries.push(ContextMenuItem::item("Copy Path", CopyPath)); - menu_entries.push(ContextMenuItem::item( + menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath)); + menu_entries.push(ContextMenuItem::action( "Copy Relative Path", CopyRelativePath, )); - menu_entries.push(ContextMenuItem::item("Reveal in Finder", RevealInFinder)); + menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder)); if let Some(clipboard_entry) = self.clipboard_entry { if clipboard_entry.worktree_id() == worktree.id() { - menu_entries.push(ContextMenuItem::item("Paste", Paste)); + menu_entries.push(ContextMenuItem::action("Paste", Paste)); } } menu_entries.push(ContextMenuItem::Separator); - menu_entries.push(ContextMenuItem::item("Rename", Rename)); + menu_entries.push(ContextMenuItem::action("Rename", Rename)); if !is_root { - menu_entries.push(ContextMenuItem::item("Delete", Delete)); + menu_entries.push(ContextMenuItem::action("Delete", Delete)); } } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index e1c8edc0c7..30a1cadbc1 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -134,7 +134,7 @@ impl TerminalButton { _action: &DeployTerminalMenu, cx: &mut ViewContext, ) { - let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)]; + let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)]; if let Some(workspace) = self.workspace.upgrade(cx) { let project = workspace.read(cx).project().read(cx); @@ -146,7 +146,7 @@ impl TerminalButton { for local_terminal_handle in local_terminal_handles { if let Some(terminal) = local_terminal_handle.upgrade(cx) { - menu_options.push(ContextMenuItem::item( + menu_options.push(ContextMenuItem::action( terminal.read(cx).title(), FocusTerminal { terminal_handle: local_terminal_handle.clone(), diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3c7d9a0ae7..b6916ee8e6 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -199,8 +199,8 @@ impl TerminalView { pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext) { let menu_entries = vec![ - ContextMenuItem::item("Clear", Clear), - ContextMenuItem::item("Close", pane::CloseActiveItem), + ContextMenuItem::action("Clear", Clear), + ContextMenuItem::action("Close", pane::CloseActiveItem), ]; self.context_menu.update(cx, |menu, cx| { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aa913986de..3d5cd906e7 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1229,10 +1229,10 @@ impl Pane { Default::default(), AnchorCorner::TopRight, vec![ - ContextMenuItem::item("Split Right", SplitRight), - ContextMenuItem::item("Split Left", SplitLeft), - ContextMenuItem::item("Split Up", SplitUp), - ContextMenuItem::item("Split Down", SplitDown), + ContextMenuItem::action("Split Right", SplitRight), + ContextMenuItem::action("Split Left", SplitLeft), + ContextMenuItem::action("Split Up", SplitUp), + ContextMenuItem::action("Split Down", SplitDown), ], cx, ); @@ -1247,9 +1247,9 @@ impl Pane { Default::default(), AnchorCorner::TopRight, vec![ - ContextMenuItem::item("Anchor Dock Right", AnchorDockRight), - ContextMenuItem::item("Anchor Dock Bottom", AnchorDockBottom), - ContextMenuItem::item("Expand Dock", ExpandDock), + ContextMenuItem::action("Anchor Dock Right", AnchorDockRight), + ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom), + ContextMenuItem::action("Expand Dock", ExpandDock), ], cx, ); @@ -1264,9 +1264,9 @@ impl Pane { Default::default(), AnchorCorner::TopRight, vec![ - ContextMenuItem::item("New File", NewFile), - ContextMenuItem::item("New Terminal", NewTerminal), - ContextMenuItem::item("New Search", NewSearch), + ContextMenuItem::action("New File", NewFile), + ContextMenuItem::action("New Terminal", NewTerminal), + ContextMenuItem::action("New Search", NewSearch), ], cx, ); @@ -1293,40 +1293,40 @@ impl Pane { AnchorCorner::TopLeft, if is_active_item { vec![ - ContextMenuItem::item("Close Active Item", CloseActiveItem), - ContextMenuItem::item("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::item("Close Clean Items", CloseCleanItems), - ContextMenuItem::item("Close Items To The Left", CloseItemsToTheLeft), - ContextMenuItem::item("Close Items To The Right", CloseItemsToTheRight), - ContextMenuItem::item("Close All Items", CloseAllItems), + ContextMenuItem::action("Close Active Item", CloseActiveItem), + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), + ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), + ContextMenuItem::action("Close All Items", CloseAllItems), ] } else { // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. vec![ - ContextMenuItem::item( + ContextMenuItem::action( "Close Inactive Item", CloseItemById { item_id: target_item_id, pane: target_pane.clone(), }, ), - ContextMenuItem::item("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::item("Close Clean Items", CloseCleanItems), - ContextMenuItem::item( + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::action( "Close Items To The Left", CloseItemsToTheLeftById { item_id: target_item_id, pane: target_pane.clone(), }, ), - ContextMenuItem::item( + ContextMenuItem::action( "Close Items To The Right", CloseItemsToTheRightById { item_id: target_item_id, pane: target_pane.clone(), }, ), - ContextMenuItem::item("Close All Items", CloseAllItems), + ContextMenuItem::action("Close All Items", CloseAllItems), ] }, cx,