keymap_ui: Infer use key equivalents (#34498)

Closes #ISSUE

This PR attempts to add workarounds for `use_key_equivalents` in the
keymap UI. First of all it makes it so that `use_key_equivalents` is
ignored when searching for a binding to replace so that replacing a
keybind with `use_key_equivalents` set to true does not result in a new
binding. Second, it attempts to infer the value of `use_key_equivalents`
off of a base binding when adding a binding by adding an optional `from`
parameter to the `KeymapUpdateOperation::Add` variant. Neither
workaround will work when the `from` binding for an add or the `target`
binding for a replace are not in the user keymap.

cc: @Anthony-Eid 

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle 2025-07-16 09:49:16 -05:00 committed by GitHub
parent 2a9a82d757
commit 21b4a2ecdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 201 additions and 119 deletions

View file

@ -10,6 +10,7 @@ use serde::Deserialize;
use serde_json::{Value, json}; use serde_json::{Value, json};
use std::borrow::Cow; use std::borrow::Cow;
use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock}; use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
use util::ResultExt as _;
use util::{ use util::{
asset_str, asset_str,
markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString}, markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString},
@ -612,19 +613,26 @@ impl KeymapFile {
KeybindUpdateOperation::Replace { KeybindUpdateOperation::Replace {
target_keybind_source: target_source, target_keybind_source: target_source,
source, source,
.. target,
} if target_source != KeybindSource::User => { } if target_source != KeybindSource::User => {
operation = KeybindUpdateOperation::Add(source); operation = KeybindUpdateOperation::Add {
source,
from: Some(target),
};
} }
// if trying to remove a keybinding that is not user-defined, treat it as creating a binding // if trying to remove a keybinding that is not user-defined, treat it as creating a binding
// that binds it to `zed::NoAction` // that binds it to `zed::NoAction`
KeybindUpdateOperation::Remove { KeybindUpdateOperation::Remove {
mut target, target,
target_keybind_source, target_keybind_source,
} if target_keybind_source != KeybindSource::User => { } if target_keybind_source != KeybindSource::User => {
target.action_name = gpui::NoAction.name(); let mut source = target.clone();
target.action_arguments.take(); source.action_name = gpui::NoAction.name();
operation = KeybindUpdateOperation::Add(target); source.action_arguments.take();
operation = KeybindUpdateOperation::Add {
source,
from: Some(target),
};
} }
_ => {} _ => {}
} }
@ -742,7 +750,10 @@ impl KeymapFile {
) )
.context("Failed to replace keybinding")?; .context("Failed to replace keybinding")?;
keymap_contents.replace_range(replace_range, &replace_value); keymap_contents.replace_range(replace_range, &replace_value);
operation = KeybindUpdateOperation::Add(source); operation = KeybindUpdateOperation::Add {
source,
from: Some(target),
};
} }
} else { } else {
log::warn!( log::warn!(
@ -752,16 +763,28 @@ impl KeymapFile {
source.keystrokes, source.keystrokes,
source_action_value, source_action_value,
); );
operation = KeybindUpdateOperation::Add(source); operation = KeybindUpdateOperation::Add {
source,
from: Some(target),
};
} }
} }
if let KeybindUpdateOperation::Add(keybinding) = operation { if let KeybindUpdateOperation::Add {
source: keybinding,
from,
} = operation
{
let mut value = serde_json::Map::with_capacity(4); let mut value = serde_json::Map::with_capacity(4);
if let Some(context) = keybinding.context { if let Some(context) = keybinding.context {
value.insert("context".to_string(), context.into()); value.insert("context".to_string(), context.into());
} }
if keybinding.use_key_equivalents { let use_key_equivalents = from.and_then(|from| {
let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
let (index, _) = find_binding(&keymap, &from, &action_value)?;
Some(keymap.0[index].use_key_equivalents)
}).unwrap_or(false);
if use_key_equivalents {
value.insert("use_key_equivalents".to_string(), true.into()); value.insert("use_key_equivalents".to_string(), true.into());
} }
@ -794,9 +817,6 @@ impl KeymapFile {
if section_context_parsed != target_context_parsed { if section_context_parsed != target_context_parsed {
continue; continue;
} }
if section.use_key_equivalents != target.use_key_equivalents {
continue;
}
let Some(bindings) = &section.bindings else { let Some(bindings) = &section.bindings else {
continue; continue;
}; };
@ -835,19 +855,27 @@ pub enum KeybindUpdateOperation<'a> {
target: KeybindUpdateTarget<'a>, target: KeybindUpdateTarget<'a>,
target_keybind_source: KeybindSource, target_keybind_source: KeybindSource,
}, },
Add(KeybindUpdateTarget<'a>), Add {
source: KeybindUpdateTarget<'a>,
from: Option<KeybindUpdateTarget<'a>>,
},
Remove { Remove {
target: KeybindUpdateTarget<'a>, target: KeybindUpdateTarget<'a>,
target_keybind_source: KeybindSource, target_keybind_source: KeybindSource,
}, },
} }
#[derive(Debug)] impl<'a> KeybindUpdateOperation<'a> {
pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
Self::Add { source, from: None }
}
}
#[derive(Debug, Clone)]
pub struct KeybindUpdateTarget<'a> { pub struct KeybindUpdateTarget<'a> {
pub context: Option<&'a str>, pub context: Option<&'a str>,
pub keystrokes: &'a [Keystroke], pub keystrokes: &'a [Keystroke],
pub action_name: &'a str, pub action_name: &'a str,
pub use_key_equivalents: bool,
pub action_arguments: Option<&'a str>, pub action_arguments: Option<&'a str>,
} }
@ -933,6 +961,7 @@ impl From<KeybindSource> for KeyBindingMetaIndex {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gpui::Keystroke;
use unindent::Unindent; use unindent::Unindent;
use crate::{ use crate::{
@ -955,37 +984,35 @@ mod tests {
KeymapFile::parse(json).unwrap(); KeymapFile::parse(json).unwrap();
} }
#[track_caller]
fn check_keymap_update(
input: impl ToString,
operation: KeybindUpdateOperation,
expected: impl ToString,
) {
let result = KeymapFile::update_keybinding(operation, input.to_string(), 4)
.expect("Update succeeded");
pretty_assertions::assert_eq!(expected.to_string(), result);
}
#[track_caller]
fn parse_keystrokes(keystrokes: &str) -> Vec<Keystroke> {
return keystrokes
.split(' ')
.map(|s| Keystroke::parse(s).expect("Keystrokes valid"))
.collect();
}
#[test] #[test]
fn keymap_update() { fn keymap_update() {
use gpui::Keystroke;
zlog::init_test(); zlog::init_test();
#[track_caller]
fn check_keymap_update(
input: impl ToString,
operation: KeybindUpdateOperation,
expected: impl ToString,
) {
let result = KeymapFile::update_keybinding(operation, input.to_string(), 4)
.expect("Update succeeded");
pretty_assertions::assert_eq!(expected.to_string(), result);
}
#[track_caller]
fn parse_keystrokes(keystrokes: &str) -> Vec<Keystroke> {
return keystrokes
.split(' ')
.map(|s| Keystroke::parse(s).expect("Keystrokes valid"))
.collect();
}
check_keymap_update( check_keymap_update(
"[]", "[]",
KeybindUpdateOperation::Add(KeybindUpdateTarget { KeybindUpdateOperation::add(KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-a"), keystrokes: &parse_keystrokes("ctrl-a"),
action_name: "zed::SomeAction", action_name: "zed::SomeAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}), }),
r#"[ r#"[
@ -1007,11 +1034,10 @@ mod tests {
} }
]"# ]"#
.unindent(), .unindent(),
KeybindUpdateOperation::Add(KeybindUpdateTarget { KeybindUpdateOperation::add(KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}), }),
r#"[ r#"[
@ -1038,11 +1064,10 @@ mod tests {
} }
]"# ]"#
.unindent(), .unindent(),
KeybindUpdateOperation::Add(KeybindUpdateTarget { KeybindUpdateOperation::add(KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: Some(r#"{"foo": "bar"}"#), action_arguments: Some(r#"{"foo": "bar"}"#),
}), }),
r#"[ r#"[
@ -1074,11 +1099,10 @@ mod tests {
} }
]"# ]"#
.unindent(), .unindent(),
KeybindUpdateOperation::Add(KeybindUpdateTarget { KeybindUpdateOperation::add(KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: Some("Zed > Editor && some_condition = true"), context: Some("Zed > Editor && some_condition = true"),
use_key_equivalents: true,
action_arguments: Some(r#"{"foo": "bar"}"#), action_arguments: Some(r#"{"foo": "bar"}"#),
}), }),
r#"[ r#"[
@ -1089,7 +1113,6 @@ mod tests {
}, },
{ {
"context": "Zed > Editor && some_condition = true", "context": "Zed > Editor && some_condition = true",
"use_key_equivalents": true,
"bindings": { "bindings": {
"ctrl-b": [ "ctrl-b": [
"zed::SomeOtherAction", "zed::SomeOtherAction",
@ -1117,14 +1140,12 @@ mod tests {
keystrokes: &parse_keystrokes("ctrl-a"), keystrokes: &parse_keystrokes("ctrl-a"),
action_name: "zed::SomeAction", action_name: "zed::SomeAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: Some(r#"{"foo": "bar"}"#), action_arguments: Some(r#"{"foo": "bar"}"#),
}, },
target_keybind_source: KeybindSource::Base, target_keybind_source: KeybindSource::Base,
@ -1163,14 +1184,12 @@ mod tests {
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "zed::SomeAction", action_name: "zed::SomeAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: Some(r#"{"foo": "bar"}"#), action_arguments: Some(r#"{"foo": "bar"}"#),
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1204,14 +1223,12 @@ mod tests {
keystrokes: &parse_keystrokes("ctrl-a"), keystrokes: &parse_keystrokes("ctrl-a"),
action_name: "zed::SomeNonexistentAction", action_name: "zed::SomeNonexistentAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1247,14 +1264,12 @@ mod tests {
keystrokes: &parse_keystrokes("ctrl-a"), keystrokes: &parse_keystrokes("ctrl-a"),
action_name: "zed::SomeAction", action_name: "zed::SomeAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("ctrl-b"), keystrokes: &parse_keystrokes("ctrl-b"),
action_name: "zed::SomeOtherAction", action_name: "zed::SomeOtherAction",
context: None, context: None,
use_key_equivalents: false,
action_arguments: Some(r#"{"foo": "bar"}"#), action_arguments: Some(r#"{"foo": "bar"}"#),
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1292,14 +1307,12 @@ mod tests {
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "foo::bar", action_name: "foo::bar",
context: Some("SomeContext"), context: Some("SomeContext"),
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("c"), keystrokes: &parse_keystrokes("c"),
action_name: "foo::baz", action_name: "foo::baz",
context: Some("SomeOtherContext"), context: Some("SomeOtherContext"),
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1336,14 +1349,12 @@ mod tests {
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "foo::bar", action_name: "foo::bar",
context: Some("SomeContext"), context: Some("SomeContext"),
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
source: KeybindUpdateTarget { source: KeybindUpdateTarget {
keystrokes: &parse_keystrokes("c"), keystrokes: &parse_keystrokes("c"),
action_name: "foo::baz", action_name: "foo::baz",
context: Some("SomeOtherContext"), context: Some("SomeOtherContext"),
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1375,7 +1386,6 @@ mod tests {
context: Some("SomeContext"), context: Some("SomeContext"),
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "foo::bar", action_name: "foo::bar",
use_key_equivalents: false,
action_arguments: None, action_arguments: None,
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1407,7 +1417,6 @@ mod tests {
context: Some("SomeContext"), context: Some("SomeContext"),
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "foo::bar", action_name: "foo::bar",
use_key_equivalents: false,
action_arguments: Some("true"), action_arguments: Some("true"),
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1450,7 +1459,6 @@ mod tests {
context: Some("SomeContext"), context: Some("SomeContext"),
keystrokes: &parse_keystrokes("a"), keystrokes: &parse_keystrokes("a"),
action_name: "foo::bar", action_name: "foo::bar",
use_key_equivalents: false,
action_arguments: Some("true"), action_arguments: Some("true"),
}, },
target_keybind_source: KeybindSource::User, target_keybind_source: KeybindSource::User,
@ -1472,4 +1480,54 @@ mod tests {
.unindent(), .unindent(),
); );
} }
#[test]
fn test_append() {
check_keymap_update(
r#"[
{
"context": "SomeOtherContext",
"use_key_equivalents": true,
"bindings": {
"b": "foo::bar",
}
},
]"#
.unindent(),
KeybindUpdateOperation::Add {
source: KeybindUpdateTarget {
context: Some("SomeContext"),
keystrokes: &parse_keystrokes("a"),
action_name: "foo::baz",
action_arguments: Some("true"),
},
from: Some(KeybindUpdateTarget {
context: Some("SomeOtherContext"),
keystrokes: &parse_keystrokes("b"),
action_name: "foo::bar",
action_arguments: None,
}),
},
r#"[
{
"context": "SomeOtherContext",
"use_key_equivalents": true,
"bindings": {
"b": "foo::bar",
}
},
{
"context": "SomeContext",
"use_key_equivalents": true,
"bindings": {
"a": [
"foo::baz",
true
]
}
}
]"#
.unindent(),
);
}
} }

View file

@ -437,17 +437,19 @@ pub fn append_top_level_array_value_in_json_text(
); );
debug_assert_eq!(cursor.node().kind(), "]"); debug_assert_eq!(cursor.node().kind(), "]");
let close_bracket_start = cursor.node().start_byte(); let close_bracket_start = cursor.node().start_byte();
cursor.goto_previous_sibling(); while cursor.goto_previous_sibling()
while (cursor.node().is_extra() || cursor.node().is_missing()) && cursor.goto_previous_sibling() && (cursor.node().is_extra() || cursor.node().is_missing())
{ && !cursor.node().is_error()
} {}
let mut comma_range = None; let mut comma_range = None;
let mut prev_item_range = None; let mut prev_item_range = None;
if cursor.node().kind() == "," { if cursor.node().kind() == "," || is_error_of_kind(&mut cursor, ",") {
comma_range = Some(cursor.node().byte_range()); comma_range = Some(cursor.node().byte_range());
while cursor.goto_previous_sibling() && cursor.node().is_extra() {} while cursor.goto_previous_sibling()
&& (cursor.node().is_extra() || cursor.node().is_missing())
{}
debug_assert_ne!(cursor.node().kind(), "["); debug_assert_ne!(cursor.node().kind(), "[");
prev_item_range = Some(cursor.node().range()); prev_item_range = Some(cursor.node().range());
@ -514,6 +516,17 @@ pub fn append_top_level_array_value_in_json_text(
replace_value.push('\n'); replace_value.push('\n');
} }
return Ok((replace_range, replace_value)); return Ok((replace_range, replace_value));
fn is_error_of_kind(cursor: &mut tree_sitter::TreeCursor<'_>, kind: &str) -> bool {
if cursor.node().kind() != "ERROR" {
return false;
}
let descendant_index = cursor.descendant_index();
let res = cursor.goto_first_child() && cursor.node().kind() == kind;
cursor.goto_descendant(descendant_index);
return res;
}
} }
pub fn to_pretty_json( pub fn to_pretty_json(

View file

@ -1,5 +1,5 @@
use std::{ use std::{
ops::{Not, Range}, ops::{Not as _, Range},
sync::Arc, sync::Arc,
}; };
@ -1602,32 +1602,45 @@ impl KeybindingEditorModal {
Ok(action_arguments) Ok(action_arguments)
} }
fn save(&mut self, cx: &mut Context<Self>) { fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<Keystroke>> {
let existing_keybind = self.editing_keybind.clone();
let fs = self.fs.clone();
let new_keystrokes = self let new_keystrokes = self
.keybind_editor .keybind_editor
.read_with(cx, |editor, _| editor.keystrokes().to_vec()); .read_with(cx, |editor, _| editor.keystrokes().to_vec());
if new_keystrokes.is_empty() { anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
self.set_error(InputError::error("Keystrokes cannot be empty"), cx); Ok(new_keystrokes)
return; }
}
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size(); fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
let new_context = self let new_context = self
.context_editor .context_editor
.read_with(cx, |input, cx| input.editor().read(cx).text(cx)); .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
let new_context = new_context.is_empty().not().then_some(new_context); let Some(context) = new_context.is_empty().not().then_some(new_context) else {
let new_context_err = new_context.as_deref().and_then(|context| { return Ok(None);
gpui::KeyBindingContextPredicate::parse(context) };
.context("Failed to parse key context") gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
.err()
}); Ok(Some(context))
if let Some(err) = new_context_err { }
// TODO: store and display as separate error
// TODO: also, should be validating on keystroke fn save(&mut self, cx: &mut Context<Self>) {
self.set_error(InputError::error(err.to_string()), cx); let existing_keybind = self.editing_keybind.clone();
return; let fs = self.fs.clone();
} let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
let new_keystrokes = match self.validate_keystrokes(cx) {
Err(err) => {
self.set_error(InputError::error(err.to_string()), cx);
return;
}
Ok(keystrokes) => keystrokes,
};
let new_context = match self.validate_context(cx) {
Err(err) => {
self.set_error(InputError::error(err.to_string()), cx);
return;
}
Ok(context) => context,
};
let new_action_args = match self.validate_action_arguments(cx) { let new_action_args = match self.validate_action_arguments(cx) {
Err(input_err) => { Err(input_err) => {
@ -2064,46 +2077,45 @@ async fn save_keybinding_update(
.await .await
.context("Failed to load keymap file")?; .context("Failed to load keymap file")?;
let operation = if !create { let existing_keystrokes = existing.keystrokes().unwrap_or_default();
let existing_keystrokes = existing.keystrokes().unwrap_or_default(); let existing_context = existing
let existing_context = existing .context
.context .as_ref()
.as_ref() .and_then(KeybindContextString::local_str);
.and_then(KeybindContextString::local_str); let existing_args = existing
let existing_args = existing .action_arguments
.action_arguments .as_ref()
.as_ref() .map(|args| args.text.as_ref());
.map(|args| args.text.as_ref());
let target = settings::KeybindUpdateTarget {
context: existing_context,
keystrokes: existing_keystrokes,
action_name: &existing.action_name,
action_arguments: existing_args,
};
let source = settings::KeybindUpdateTarget {
context: new_context,
keystrokes: new_keystrokes,
action_name: &existing.action_name,
action_arguments: new_args,
};
let operation = if !create {
settings::KeybindUpdateOperation::Replace { settings::KeybindUpdateOperation::Replace {
target: settings::KeybindUpdateTarget { target,
context: existing_context,
keystrokes: existing_keystrokes,
action_name: &existing.action_name,
use_key_equivalents: false,
action_arguments: existing_args,
},
target_keybind_source: existing target_keybind_source: existing
.source .source
.as_ref() .as_ref()
.map(|(source, _name)| *source) .map(|(source, _name)| *source)
.unwrap_or(KeybindSource::User), .unwrap_or(KeybindSource::User),
source: settings::KeybindUpdateTarget { source,
context: new_context,
keystrokes: new_keystrokes,
action_name: &existing.action_name,
use_key_equivalents: false,
action_arguments: new_args,
},
} }
} else { } else {
settings::KeybindUpdateOperation::Add(settings::KeybindUpdateTarget { settings::KeybindUpdateOperation::Add {
context: new_context, source,
keystrokes: new_keystrokes, from: Some(target),
action_name: &existing.action_name, }
use_key_equivalents: false,
action_arguments: new_args,
})
}; };
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)
@ -2137,7 +2149,6 @@ async fn remove_keybinding(
.and_then(KeybindContextString::local_str), .and_then(KeybindContextString::local_str),
keystrokes, keystrokes,
action_name: &existing.action_name, action_name: &existing.action_name,
use_key_equivalents: false,
action_arguments: existing action_arguments: existing
.action_arguments .action_arguments
.as_ref() .as_ref()