Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/keymap.rs')
-rw-r--r--helix-term/src/keymap.rs136
1 files changed, 81 insertions, 55 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index d8227b50..aae78933 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -106,7 +106,7 @@ impl KeyTrieNode {
(events.join(", "), desc)
})
.collect();
- Info::new(self.name.clone(), &body)
+ Info::new(&self.name, &body)
}
}
@@ -177,19 +177,6 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
.map_err(serde::de::Error::custom)?,
)
}
-
- // Prevent macro keybindings from being used in command sequences.
- // This is meant to be a temporary restriction pending a larger
- // refactor of how command sequences are executed.
- if commands
- .iter()
- .any(|cmd| matches!(cmd, MappableCommand::Macro { .. }))
- {
- return Err(serde::de::Error::custom(
- "macro keybindings may not be used in command sequences",
- ));
- }
-
Ok(KeyTrie::Sequence(commands))
}
@@ -212,7 +199,6 @@ impl KeyTrie {
// recursively visit all nodes in keymap
fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
match node {
- KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {}
KeyTrie::MappableCommand(cmd) => {
let name = cmd.name();
if name != "no_op" {
@@ -286,8 +272,43 @@ pub enum KeymapResult {
/// A map of command names to keybinds that will execute the command.
pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
+// TODO name
+#[derive(Eq, Hash, PartialEq, Clone, Debug)]
+pub enum Domain {
+ Mode(Mode),
+ Component(&'static str),
+}
+
+const REMAPPABLE_COMPONENTS: [&'static str; 3] = [
+ crate::ui::DYNAMIC_PICKER_ID,
+ crate::ui::PICKER_ID,
+ // TODO: make it a constant
+ "buffer-picker",
+];
+
+impl<'de> Deserialize<'de> for Domain {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ if let Ok(mode) = s.parse::<Mode>() {
+ return Ok(Domain::Mode(mode));
+ } else if let Some(name) = REMAPPABLE_COMPONENTS
+ .iter()
+ .find(|name| **name == s.as_str())
+ {
+ Ok(Domain::Component(name))
+ } else {
+ Err(serde::de::Error::custom(format!(
+ "Unknown keymap domain {s}. Expected a mode or component name"
+ )))
+ }
+ }
+}
+
pub struct Keymaps {
- pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>,
+ pub map: Box<dyn DynAccess<HashMap<Domain, KeyTrie>>>,
/// Stores pending keys waiting for the next key. This is relative to a
/// sticky node if one is in use.
state: Vec<KeyEvent>,
@@ -296,7 +317,7 @@ pub struct Keymaps {
}
impl Keymaps {
- pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self {
+ pub fn new(map: Box<dyn DynAccess<HashMap<Domain, KeyTrie>>>) -> Self {
Self {
map,
state: Vec::new(),
@@ -304,7 +325,7 @@ impl Keymaps {
}
}
- pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> {
+ pub fn map(&self) -> DynGuard<HashMap<Domain, KeyTrie>> {
self.map.load()
}
@@ -317,23 +338,24 @@ impl Keymaps {
self.sticky.as_ref()
}
- pub fn contains_key(&self, mode: Mode, key: KeyEvent) -> bool {
- let keymaps = &*self.map();
- let keymap = &keymaps[&mode];
- keymap
- .search(self.pending())
- .and_then(KeyTrie::node)
- .is_some_and(|node| node.contains_key(&key))
+ pub fn get_by_mode(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult {
+ self.get(Domain::Mode(mode), key)
+ }
+
+ pub fn get_by_component_id(&mut self, id: &'static str, key: KeyEvent) -> KeymapResult {
+ self.get(Domain::Component(id), key)
}
/// Lookup `key` in the keymap to try and find a command to execute. Escape
/// key cancels pending keystrokes. If there are no pending keystrokes but a
/// sticky node is in use, it will be cleared.
- pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult {
- // TODO: remove the sticky part and look up manually
+ fn get(&mut self, domain: Domain, key: KeyEvent) -> KeymapResult {
let keymaps = &*self.map();
- let keymap = &keymaps[&mode];
+ let Some(keymap) = keymaps.get(&domain) else {
+ return KeymapResult::NotFound;
+ };
+ // TODO: remove the sticky part and look up manually
if key!(Esc) == key {
if !self.state.is_empty() {
// Note that Esc is not included here
@@ -342,7 +364,7 @@ impl Keymaps {
self.sticky = None;
}
- let first = self.state.first().unwrap_or(&key);
+ let first = self.state.get(0).unwrap_or(&key);
let trie_node = match self.sticky {
Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
None => Cow::Borrowed(keymap),
@@ -388,7 +410,7 @@ impl Default for Keymaps {
}
/// Merge default config keys with user overwritten keys for custom user config.
-pub fn merge_keys(dst: &mut HashMap<Mode, KeyTrie>, mut delta: HashMap<Mode, KeyTrie>) {
+pub fn merge_keys(dst: &mut HashMap<Domain, KeyTrie>, mut delta: HashMap<Domain, KeyTrie>) {
for (mode, keys) in dst {
keys.merge_nodes(
delta
@@ -423,15 +445,15 @@ mod tests {
#[test]
fn merge_partial_keys() {
let keymap = hashmap! {
- Mode::Normal => keymap!({ "Normal mode"
- "i" => normal_mode,
- "无" => insert_mode,
- "z" => jump_backward,
- "g" => { "Merge into goto mode"
- "$" => goto_line_end,
- "g" => delete_char_forward,
- },
- })
+ Domain::Mode(Mode::Normal) => keymap!({ "Normal mode"
+ "i" => normal_mode,
+ "无" => insert_mode,
+ "z" => jump_backward,
+ "g" => { "Merge into goto mode"
+ "$" => goto_line_end,
+ "g" => delete_char_forward,
+ },
+ })
};
let mut merged_keyamp = default();
merge_keys(&mut merged_keyamp, keymap.clone());
@@ -439,23 +461,23 @@ mod tests {
let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone())));
assert_eq!(
- keymap.get(Mode::Normal, key!('i')),
+ keymap.get_by_mode(Mode::Normal, key!('i')),
KeymapResult::Matched(MappableCommand::normal_mode),
"Leaf should replace leaf"
);
assert_eq!(
- keymap.get(Mode::Normal, key!('无')),
+ keymap.get_by_mode(Mode::Normal, key!('无')),
KeymapResult::Matched(MappableCommand::insert_mode),
"New leaf should be present in merged keymap"
);
// Assumes that z is a node in the default keymap
assert_eq!(
- keymap.get(Mode::Normal, key!('z')),
+ keymap.get_by_mode(Mode::Normal, key!('z')),
KeymapResult::Matched(MappableCommand::jump_backward),
"Leaf should replace node"
);
- let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
+ let keymap = merged_keyamp.get_mut(&Domain::Mode(Mode::Normal)).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
keymap.search(&[key!('g'), key!('$')]).unwrap(),
@@ -477,7 +499,7 @@ mod tests {
assert!(
merged_keyamp
- .get(&Mode::Normal)
+ .get(&Domain::Mode(Mode::Normal))
.and_then(|key_trie| key_trie.node())
.unwrap()
.len()
@@ -485,7 +507,7 @@ mod tests {
);
assert!(
merged_keyamp
- .get(&Mode::Insert)
+ .get(&Domain::Mode(Mode::Insert))
.and_then(|key_trie| key_trie.node())
.unwrap()
.len()
@@ -496,19 +518,19 @@ mod tests {
#[test]
fn order_should_be_set() {
let keymap = hashmap! {
- Mode::Normal => keymap!({ "Normal mode"
- "space" => { ""
- "s" => { ""
- "v" => vsplit,
- "c" => hsplit,
+ Domain::Mode(Mode::Normal) => keymap!({ "Normal mode"
+ "space" => { ""
+ "s" => { ""
+ "v" => vsplit,
+ "c" => hsplit,
+ },
},
- },
- })
+ })
};
let mut merged_keyamp = default();
merge_keys(&mut merged_keyamp, keymap.clone());
assert_ne!(keymap, merged_keyamp);
- let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
+ let keymap = merged_keyamp.get_mut(&Domain::Mode(Mode::Normal)).unwrap();
// Make sure mapping works
assert_eq!(
keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(),
@@ -523,7 +545,7 @@ mod tests {
#[test]
fn aliased_modes_are_same_in_default_keymap() {
let keymaps = Keymaps::default().map();
- let root = keymaps.get(&Mode::Normal).unwrap();
+ let root = keymaps.get(&Domain::Mode(Mode::Normal)).unwrap();
assert_eq!(
root.search(&[key!(' '), key!('w')]).unwrap(),
root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
@@ -601,7 +623,11 @@ mod tests {
MappableCommand::select_all,
MappableCommand::Typable {
name: "pipe".to_string(),
- args: "sed -E 's/\\s+$//g'".to_string(),
+ args: vec!{
+ "sed".to_string(),
+ "-E".to_string(),
+ "'s/\\s+$//g'".to_string()
+ },
doc: "".to_string(),
},
})