keymap_ui: Creating keybinds (#34138)

Adds support for creating new keybinds in the keymap UI. "Create" means
two different things for existing bindings, and unbound actions.
- For existing bindings, it is essentially a duplicate + edit
- For unbound actions, it is the creation of a binding

Release Notes:

- N/A
This commit is contained in:
Ben Kunkle 2025-07-09 11:24:33 -05:00 committed by GitHub
parent e2b9dfa89c
commit 6d26f107dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -45,6 +45,8 @@ actions!(
[ [
/// Edits the selected key binding. /// Edits the selected key binding.
EditBinding, EditBinding,
/// Creates a new key binding for the selected action.
CreateBinding,
/// Copies the action name to clipboard. /// Copies the action name to clipboard.
CopyAction, CopyAction,
/// Copies the context predicate to clipboard. /// Copies the context predicate to clipboard.
@ -466,11 +468,16 @@ impl KeymapEditor {
} }
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) { fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
self.edit_selected_keybinding(window, cx); self.open_edit_keybinding_modal(false, window, cx);
} }
fn edit_selected_keybinding(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn open_edit_keybinding_modal(
let Some(keybind) = self.selected_binding() else { &mut self,
create: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(keybind) = self.selected_binding().cloned() else {
return; return;
}; };
self.workspace self.workspace
@ -479,7 +486,7 @@ impl KeymapEditor {
let workspace_weak = cx.weak_entity(); let workspace_weak = cx.weak_entity();
workspace.toggle_modal(window, cx, |window, cx| { workspace.toggle_modal(window, cx, |window, cx| {
let modal = let modal =
KeybindingEditorModal::new(keybind.clone(), workspace_weak, fs, window, cx); KeybindingEditorModal::new(create, keybind, workspace_weak, fs, window, cx);
window.focus(&modal.focus_handle(cx)); window.focus(&modal.focus_handle(cx));
modal modal
}); });
@ -488,7 +495,11 @@ impl KeymapEditor {
} }
fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) { fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
self.edit_selected_keybinding(window, cx); self.open_edit_keybinding_modal(false, window, cx);
}
fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
self.open_edit_keybinding_modal(true, window, cx);
} }
fn copy_context_to_clipboard( fn copy_context_to_clipboard(
@ -600,6 +611,7 @@ impl Render for KeymapEditor {
.on_action(cx.listener(Self::focus_search)) .on_action(cx.listener(Self::focus_search))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::edit_binding)) .on_action(cx.listener(Self::edit_binding))
.on_action(cx.listener(Self::create_binding))
.on_action(cx.listener(Self::copy_action_to_clipboard)) .on_action(cx.listener(Self::copy_action_to_clipboard))
.on_action(cx.listener(Self::copy_context_to_clipboard)) .on_action(cx.listener(Self::copy_context_to_clipboard))
.size_full() .size_full()
@ -778,6 +790,7 @@ impl RenderOnce for SyntaxHighlightedText {
} }
struct KeybindingEditorModal { struct KeybindingEditorModal {
creating: bool,
editing_keybind: ProcessedKeybinding, editing_keybind: ProcessedKeybinding,
keybind_editor: Entity<KeystrokeInput>, keybind_editor: Entity<KeystrokeInput>,
context_editor: Entity<Editor>, context_editor: Entity<Editor>,
@ -798,6 +811,7 @@ impl Focusable for KeybindingEditorModal {
impl KeybindingEditorModal { impl KeybindingEditorModal {
pub fn new( pub fn new(
create: bool,
editing_keybind: ProcessedKeybinding, editing_keybind: ProcessedKeybinding,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
@ -864,6 +878,7 @@ impl KeybindingEditorModal {
}); });
Self { Self {
creating: create,
editing_keybind, editing_keybind,
fs, fs,
keybind_editor, keybind_editor,
@ -902,8 +917,11 @@ impl KeybindingEditorModal {
return; return;
} }
let create = self.creating;
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
if let Err(err) = save_keybinding_update( if let Err(err) = save_keybinding_update(
create,
existing_keybind, existing_keybind,
&new_keystrokes, &new_keystrokes,
new_context.as_deref(), new_context.as_deref(),
@ -1142,6 +1160,7 @@ async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp)
} }
async fn save_keybinding_update( async fn save_keybinding_update(
create: bool,
existing: ProcessedKeybinding, existing: ProcessedKeybinding,
new_keystrokes: &[Keystroke], new_keystrokes: &[Keystroke],
new_context: Option<&str>, new_context: Option<&str>,
@ -1168,7 +1187,7 @@ async fn save_keybinding_update(
.as_ref() .as_ref()
.map(|input| input.text.as_ref()); .map(|input| input.text.as_ref());
let operation = if existing.ui_key_binding.is_some() { let operation = if !create {
settings::KeybindUpdateOperation::Replace { settings::KeybindUpdateOperation::Replace {
target: settings::KeybindUpdateTarget { target: settings::KeybindUpdateTarget {
context: existing_context, context: existing_context,
@ -1190,7 +1209,13 @@ async fn save_keybinding_update(
}, },
} }
} else { } else {
anyhow::bail!("Adding new bindings not implemented yet"); settings::KeybindUpdateOperation::Add(settings::KeybindUpdateTarget {
context: new_context,
keystrokes: new_keystrokes,
action_name: &existing.action_name,
use_key_equivalents: false,
input,
})
}; };
let updated_keymap_contents = let updated_keymap_contents =
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size) settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
@ -1378,16 +1403,19 @@ fn build_keybind_context_menu(
return menu; return menu;
}; };
let selected_binding_has_context = selected_binding let selected_binding_has_no_context = selected_binding
.context .context
.as_ref() .as_ref()
.and_then(KeybindContextString::local) .and_then(KeybindContextString::local)
.is_some(); .is_none();
menu.action("Edit Binding", Box::new(EditBinding)) let selected_binding_is_unbound = selected_binding.ui_key_binding.is_none();
menu.action_disabled_when(selected_binding_is_unbound, "Edit", Box::new(EditBinding))
.action("Create", Box::new(CreateBinding))
.action("Copy action", Box::new(CopyAction)) .action("Copy action", Box::new(CopyAction))
.action_disabled_when( .action_disabled_when(
!selected_binding_has_context, selected_binding_has_no_context,
"Copy Context", "Copy Context",
Box::new(CopyContext), Box::new(CopyContext),
) )