diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 4053d41669..159756c975 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -11,8 +11,8 @@ use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter, - FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, - StyledText, Subscription, WeakEntity, actions, div, + FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, MouseButton, + Point, ScrollStrategy, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div, }; use language::{Language, LanguageConfig, ToOffset as _}; use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets}; @@ -21,7 +21,7 @@ use util::ResultExt; use ui::{ ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, ParentElement as _, Render, - SharedString, Styled as _, Tooltip, Window, prelude::*, right_click_menu, + SharedString, Styled as _, Tooltip, Window, prelude::*, }; use workspace::{ Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _, @@ -187,7 +187,7 @@ struct ConflictState { } impl ConflictState { - fn new(key_bindings: &Vec) -> Self { + fn new(key_bindings: &[ProcessedKeybinding]) -> Self { let mut action_keybind_mapping: HashMap<_, Vec> = HashMap::default(); key_bindings @@ -255,6 +255,7 @@ struct KeymapEditor { filter_editor: Entity, keystroke_editor: Entity, selected_index: Option, + context_menu: Option<(Entity, Point, Subscription)>, } impl EventEmitter<()> for KeymapEditor {} @@ -267,8 +268,6 @@ impl Focusable for KeymapEditor { impl KeymapEditor { fn new(workspace: WeakEntity, window: &mut Window, cx: &mut Context) -> Self { - let focus_handle = cx.focus_handle(); - let _keymap_subscription = cx.observe_global::(Self::update_keybindings); let table_interaction_state = TableInteractionState::new(window, cx); @@ -311,12 +310,13 @@ impl KeymapEditor { search_mode: SearchMode::default(), string_match_candidates: Arc::new(vec![]), matches: vec![], - focus_handle: focus_handle.clone(), + focus_handle: cx.focus_handle(), _keymap_subscription, table_interaction_state, filter_editor, keystroke_editor, selected_index: None, + context_menu: None, }; this.update_keybindings(cx); @@ -593,6 +593,68 @@ impl KeymapEditor { .and_then(|keybind_index| self.keybindings.get(keybind_index)) } + fn select_index(&mut self, index: usize, cx: &mut Context) { + if self.selected_index != Some(index) { + self.selected_index = Some(index); + cx.notify(); + } + } + + fn create_context_menu( + &mut self, + position: Point, + window: &mut Window, + cx: &mut Context, + ) { + self.context_menu = self.selected_binding().map(|selected_binding| { + let selected_binding_has_no_context = selected_binding + .context + .as_ref() + .and_then(KeybindContextString::local) + .is_none(); + + let selected_binding_is_unbound = selected_binding.ui_key_binding.is_none(); + + let context_menu = ContextMenu::build(window, cx, |menu, _window, _cx| { + menu.action_disabled_when( + selected_binding_is_unbound, + "Edit", + Box::new(EditBinding), + ) + .action("Create", Box::new(CreateBinding)) + .action("Copy action", Box::new(CopyAction)) + .action_disabled_when( + selected_binding_has_no_context, + "Copy Context", + Box::new(CopyContext), + ) + }); + + let context_menu_handle = context_menu.focus_handle(cx); + window.defer(cx, move |window, _cx| window.focus(&context_menu_handle)); + let subscription = cx.subscribe_in( + &context_menu, + window, + |this, _, _: &DismissEvent, window, cx| { + this.dismiss_context_menu(window, cx); + }, + ); + (context_menu, position, subscription) + }); + + cx.notify(); + } + + fn dismiss_context_menu(&mut self, window: &mut Window, cx: &mut Context) { + self.context_menu.take(); + window.focus(&self.focus_handle); + cx.notify(); + } + + fn context_menu_deployed(&self) -> bool { + self.context_menu.is_some() + } + fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context) { if let Some(selected) = self.selected_index { let selected = selected + 1; @@ -975,6 +1037,7 @@ impl Render for KeymapEditor { "keymap-editor-table", row_count, cx.processor(move |this, range: Range, _window, cx| { + let context_menu_deployed = this.context_menu_deployed(); range .filter_map(|index| { let candidate_id = this.matches.get(index)?.candidate_id; @@ -983,21 +1046,23 @@ impl Render for KeymapEditor { let action = div() .child(binding.action_name.clone()) .id(("keymap action", index)) - .tooltip({ - let action_name = binding.action_name.clone(); - let action_docs = binding.action_docs; - move |_, cx| { - let action_tooltip = Tooltip::new( - command_palette::humanize_action_name( - &action_name, - ), - ); - let action_tooltip = match action_docs { - Some(docs) => action_tooltip.meta(docs), - None => action_tooltip, - }; - cx.new(|_| action_tooltip).into() - } + .when(!context_menu_deployed, |this| { + this.tooltip({ + let action_name = binding.action_name.clone(); + let action_docs = binding.action_docs; + move |_, cx| { + let action_tooltip = Tooltip::new( + command_palette::humanize_action_name( + &action_name, + ), + ); + let action_tooltip = match action_docs { + Some(docs) => action_tooltip.meta(docs), + None => action_tooltip, + }; + cx.new(|_| action_tooltip).into() + } + }) }) .into_any_element(); let keystrokes = binding.ui_key_binding.clone().map_or( @@ -1023,7 +1088,7 @@ impl Render for KeymapEditor { div() .id(("keymap context", index)) .child(context.clone()) - .when(is_local, |this| { + .when(is_local && !context_menu_deployed, |this| { this.tooltip(Tooltip::element({ move |_, _| { context.clone().into_any_element() @@ -1055,9 +1120,30 @@ impl Render for KeymapEditor { let row = row .id(("keymap-table-row", row_index)) + .on_any_mouse_down(cx.listener( + move |this, + mouse_down_event: &gpui::MouseDownEvent, + window, + cx| { + match mouse_down_event.button { + MouseButton::Left => { + this.select_index(row_index, cx); + } + + MouseButton::Right => { + this.select_index(row_index, cx); + this.create_context_menu( + mouse_down_event.position, + window, + cx, + ); + } + _ => {} + } + }, + )) .on_click(cx.listener( move |this, event: &ClickEvent, window, cx| { - this.selected_index = Some(row_index); if event.up.click_count == 2 { this.open_edit_keybinding_modal(false, window, cx); } @@ -1071,18 +1157,23 @@ impl Render for KeymapEditor { row.border_color(cx.theme().colors().panel_focused_border) }); - right_click_menu(("keymap-table-row-menu", row_index)) - .trigger(move |_, _, _| row) - .menu({ - let this = cx.weak_entity(); - move |window, cx| { - build_keybind_context_menu(&this, row_index, window, cx) - } - }) - .into_any_element() + row.into_any_element() }), ), ) + .on_scroll_wheel(cx.listener(|this, _, _, cx| { + this.context_menu.take(); + cx.notify(); + })) + .children(self.context_menu.as_ref().map(|(menu, position, _)| { + deferred( + anchored() + .position(*position) + .anchor(gpui::Corner::TopLeft) + .child(menu.clone()), + ) + .with_priority(1) + })) } } @@ -1895,53 +1986,6 @@ impl Render for KeystrokeInput { } } -fn build_keybind_context_menu( - this: &WeakEntity, - item_idx: usize, - window: &mut Window, - cx: &mut App, -) -> Entity { - ContextMenu::build(window, cx, |menu, _window, cx| { - let selected_binding = this - .update(cx, |this, _cx| { - this.selected_index = Some(item_idx); - this.selected_binding().cloned() - }) - .ok() - .flatten(); - - let Some(selected_binding) = selected_binding else { - return menu; - }; - - let selected_binding_has_no_context = selected_binding - .context - .as_ref() - .and_then(KeybindContextString::local) - .is_none(); - - let selected_binding_is_unbound_action = selected_binding.ui_key_binding.is_none(); - - menu.action_disabled_when( - selected_binding_is_unbound_action, - "Edit", - Box::new(EditBinding), - ) - .action("Create", Box::new(CreateBinding)) - .action_disabled_when( - selected_binding_is_unbound_action, - "Delete", - Box::new(DeleteBinding), - ) - .action("Copy action", Box::new(CopyAction)) - .action_disabled_when( - selected_binding_has_no_context, - "Copy Context", - Box::new(CopyContext), - ) - }) -} - fn collect_contexts_from_assets() -> Vec { let mut keymap_assets = vec![ util::asset_str::(settings::DEFAULT_KEYMAP_PATH),