Detailed changes
@@ -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,
+ ),
]
};
@@ -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<dyn Action>),
+ Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
+}
+
+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<dyn Action>,
+ action: ContextMenuItemAction,
},
Static(StaticItem),
Separator,
}
impl ContextMenuItem {
- pub fn item(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
+ pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
+ Self::Item {
+ label: label.into(),
+ action: ContextMenuItemAction::Action(Box::new(action)),
+ }
+ }
+
+ pub fn handler(
+ label: impl Into<ContextMenuItemLabel>,
+ handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
+ ) -> Self {
Self::Item {
label: label.into(),
- action: Box::new(action),
+ action: ContextMenuItemAction::Handler(Arc::new(handler)),
}
}
@@ -91,7 +115,10 @@ impl ContextMenuItem {
fn action_id(&self) -> Option<TypeId> {
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<Self>) {
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::<MenuItem, ContextMenu>::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()
@@ -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::<Settings>().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(
@@ -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,
);
@@ -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));
}
}
@@ -134,7 +134,7 @@ impl TerminalButton {
_action: &DeployTerminalMenu,
cx: &mut ViewContext<Self>,
) {
- 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(),
@@ -199,8 +199,8 @@ impl TerminalView {
pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
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| {
@@ -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,