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.rs994
1 files changed, 713 insertions, 281 deletions
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index d8227b50..1059feae 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -1,24 +1,137 @@
-pub mod default;
-pub mod macros;
-
pub use crate::commands::MappableCommand;
-use arc_swap::{
- access::{DynAccess, DynGuard},
- ArcSwap,
-};
+use crate::config::Config;
+use helix_core::hashmap;
use helix_view::{document::Mode, info::Info, input::KeyEvent};
use serde::Deserialize;
use std::{
borrow::Cow,
collections::{BTreeSet, HashMap},
ops::{Deref, DerefMut},
- sync::Arc,
};
-pub use default::default;
-use macros::key;
+#[macro_export]
+macro_rules! key {
+ ($key:ident) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::$key,
+ modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
+ }
+ };
+ ($($ch:tt)*) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
+ modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! shift {
+ ($key:ident) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::$key,
+ modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
+ }
+ };
+ ($($ch:tt)*) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
+ modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! ctrl {
+ ($key:ident) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::$key,
+ modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
+ }
+ };
+ ($($ch:tt)*) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
+ modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! alt {
+ ($key:ident) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::$key,
+ modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
+ }
+ };
+ ($($ch:tt)*) => {
+ ::helix_view::input::KeyEvent {
+ code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
+ modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
+ }
+ };
+}
+
+/// Macro for defining the root of a `Keymap` object. Example:
+///
+/// ```
+/// # use helix_core::hashmap;
+/// # use helix_term::keymap;
+/// # use helix_term::keymap::Keymap;
+/// let normal_mode = keymap!({ "Normal mode"
+/// "i" => insert_mode,
+/// "g" => { "Goto"
+/// "g" => goto_file_start,
+/// "e" => goto_file_end,
+/// },
+/// "j" | "down" => move_line_down,
+/// });
+/// let keymap = Keymap::new(normal_mode);
+/// ```
+#[macro_export]
+macro_rules! keymap {
+ (@trie $cmd:ident) => {
+ $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
+ };
+
+ (@trie
+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
+ ) => {
+ keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
+ };
+
+ (@trie [$($cmd:ident),* $(,)?]) => {
+ $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*])
+ };
+
+ (
+ { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
+ ) => {
+ // modified from the hashmap! macro
+ {
+ let _cap = hashmap!(@count $($($key),+),*);
+ let mut _map = ::std::collections::HashMap::with_capacity(_cap);
+ let mut _order = ::std::vec::Vec::with_capacity(_cap);
+ $(
+ $(
+ let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap();
+ let _duplicate = _map.insert(
+ _key,
+ keymap!(@trie $value)
+ );
+ assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap());
+ _order.push(_key);
+ )+
+ )*
+ let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
+ $( _node.is_sticky = $sticky; )?
+ $crate::keymap::KeyTrie::Node(_node)
+ }
+ };
+}
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
pub struct KeyTrieNode {
/// A label for keys coming under this node, like "Goto mode"
name: String,
@@ -52,6 +165,10 @@ impl KeyTrieNode {
}
}
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
/// Merge another Node in. Leaves and subnodes from the other node replace
/// corresponding keyevent in self, except when both other and self have
/// subnodes for same key. In that case the merge is recursive.
@@ -73,40 +190,49 @@ impl KeyTrieNode {
}
pub fn infobox(&self) -> Info {
- let mut body: Vec<(BTreeSet<KeyEvent>, &str)> = Vec::with_capacity(self.len());
+ let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
for (&key, trie) in self.iter() {
let desc = match trie {
- KeyTrie::MappableCommand(cmd) => {
+ KeyTrie::Leaf(cmd) => {
if cmd.name() == "no_op" {
continue;
}
cmd.doc()
}
- KeyTrie::Node(n) => &n.name,
+ KeyTrie::Node(n) => n.name(),
KeyTrie::Sequence(_) => "[Multiple commands]",
};
- match body.iter().position(|(_, d)| d == &desc) {
+ match body.iter().position(|(d, _)| d == &desc) {
Some(pos) => {
- body[pos].0.insert(key);
+ body[pos].1.insert(key);
}
- None => body.push((BTreeSet::from([key]), desc)),
+ None => body.push((desc, BTreeSet::from([key]))),
}
}
- body.sort_unstable_by_key(|(keys, _)| {
+ body.sort_unstable_by_key(|(_, keys)| {
self.order
.iter()
.position(|&k| k == *keys.iter().next().unwrap())
.unwrap()
});
+ let prefix = format!("{} ", self.name());
+ if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
+ body = body
+ .into_iter()
+ .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
+ .collect();
+ }
+ Info::from_keymap(self.name(), body)
+ }
+ /// Get a reference to the key trie node's order.
+ pub fn order(&self) -> &[KeyEvent] {
+ self.order.as_slice()
+ }
+}
- let body: Vec<_> = body
- .into_iter()
- .map(|(events, desc)| {
- let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
- (events.join(", "), desc)
- })
- .collect();
- Info::new(self.name.clone(), &body)
+impl Default for KeyTrieNode {
+ fn default() -> Self {
+ Self::new("", HashMap::new(), Vec::new())
}
}
@@ -130,122 +256,26 @@ impl DerefMut for KeyTrieNode {
}
}
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(untagged)]
pub enum KeyTrie {
- MappableCommand(MappableCommand),
+ Leaf(MappableCommand),
Sequence(Vec<MappableCommand>),
Node(KeyTrieNode),
}
-impl<'de> Deserialize<'de> for KeyTrie {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- deserializer.deserialize_any(KeyTrieVisitor)
- }
-}
-
-struct KeyTrieVisitor;
-
-impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
- type Value = KeyTrie;
-
- fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(formatter, "a command, list of commands, or sub-keymap")
- }
-
- fn visit_str<E>(self, command: &str) -> Result<Self::Value, E>
- where
- E: serde::de::Error,
- {
- command
- .parse::<MappableCommand>()
- .map(KeyTrie::MappableCommand)
- .map_err(E::custom)
- }
-
- fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
- where
- S: serde::de::SeqAccess<'de>,
- {
- let mut commands = Vec::new();
- while let Some(command) = seq.next_element::<String>()? {
- commands.push(
- command
- .parse::<MappableCommand>()
- .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))
- }
-
- fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
- where
- M: serde::de::MapAccess<'de>,
- {
- let mut mapping = HashMap::new();
- let mut order = Vec::new();
- while let Some((key, value)) = map.next_entry::<KeyEvent, KeyTrie>()? {
- mapping.insert(key, value);
- order.push(key);
- }
- Ok(KeyTrie::Node(KeyTrieNode::new("", mapping, order)))
- }
-}
-
impl KeyTrie {
- pub fn reverse_map(&self) -> ReverseKeymap {
- // 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" {
- cmd_map.entry(name.into()).or_default().push(keys.clone())
- }
- }
- KeyTrie::Node(next) => {
- for (key, trie) in &next.map {
- keys.push(*key);
- map_node(cmd_map, trie, keys);
- keys.pop();
- }
- }
- KeyTrie::Sequence(_) => {}
- };
- }
-
- let mut res = HashMap::new();
- map_node(&mut res, self, &mut Vec::new());
- res
- }
-
pub fn node(&self) -> Option<&KeyTrieNode> {
match *self {
KeyTrie::Node(ref node) => Some(node),
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}
}
pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
match *self {
KeyTrie::Node(ref mut node) => Some(node),
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}
}
@@ -262,7 +292,7 @@ impl KeyTrie {
trie = match trie {
KeyTrie::Node(map) => map.get(key),
// leaf encountered while keys left to process
- KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
}?
}
Some(trie)
@@ -270,7 +300,7 @@ impl KeyTrie {
}
#[derive(Debug, Clone, PartialEq)]
-pub enum KeymapResult {
+pub enum KeymapResultKind {
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
Pending(KeyTrieNode),
Matched(MappableCommand),
@@ -283,127 +313,569 @@ pub enum KeymapResult {
Cancelled(Vec<KeyEvent>),
}
-/// A map of command names to keybinds that will execute the command.
-pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
+/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
+/// reference to the sticky node if one is currently active.
+#[derive(Debug)]
+pub struct KeymapResult<'a> {
+ pub kind: KeymapResultKind,
+ pub sticky: Option<&'a KeyTrieNode>,
+}
-pub struct Keymaps {
- pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>,
+impl<'a> KeymapResult<'a> {
+ pub fn new(kind: KeymapResultKind, sticky: Option<&'a KeyTrieNode>) -> Self {
+ Self { kind, sticky }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+pub struct Keymap {
+ /// Always a Node
+ #[serde(flatten)]
+ root: KeyTrie,
/// Stores pending keys waiting for the next key. This is relative to a
/// sticky node if one is in use.
+ #[serde(skip)]
state: Vec<KeyEvent>,
/// Stores the sticky node if one is activated.
- pub sticky: Option<KeyTrieNode>,
+ #[serde(skip)]
+ sticky: Option<KeyTrieNode>,
}
-impl Keymaps {
- pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self {
- Self {
- map,
+impl Keymap {
+ pub fn new(root: KeyTrie) -> Self {
+ Keymap {
+ root,
state: Vec::new(),
sticky: None,
}
}
- pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> {
- self.map.load()
+ pub fn reverse_map(&self) -> HashMap<String, Vec<Vec<KeyEvent>>> {
+ // recursively visit all nodes in keymap
+ fn map_node(
+ cmd_map: &mut HashMap<String, Vec<Vec<KeyEvent>>>,
+ node: &KeyTrie,
+ keys: &mut Vec<KeyEvent>,
+ ) {
+ match node {
+ KeyTrie::Leaf(cmd) => match cmd {
+ MappableCommand::Typable { name, .. } => {
+ cmd_map.entry(name.into()).or_default().push(keys.clone())
+ }
+ MappableCommand::Static { name, .. } => cmd_map
+ .entry(name.to_string())
+ .or_default()
+ .push(keys.clone()),
+ },
+ KeyTrie::Node(next) => {
+ for (key, trie) in &next.map {
+ keys.push(*key);
+ map_node(cmd_map, trie, keys);
+ keys.pop();
+ }
+ }
+ KeyTrie::Sequence(_) => {}
+ };
+ }
+
+ let mut res = HashMap::new();
+ map_node(&mut res, &self.root, &mut Vec::new());
+ res
}
- /// Returns list of keys waiting to be disambiguated in current mode.
- pub fn pending(&self) -> &[KeyEvent] {
- &self.state
+ pub fn root(&self) -> &KeyTrie {
+ &self.root
}
pub fn sticky(&self) -> Option<&KeyTrieNode> {
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))
+ /// Returns list of keys waiting to be disambiguated.
+ pub fn pending(&self) -> &[KeyEvent] {
+ &self.state
}
/// 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
- let keymaps = &*self.map();
- let keymap = &keymaps[&mode];
-
+ pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
if key!(Esc) == key {
if !self.state.is_empty() {
- // Note that Esc is not included here
- return KeymapResult::Cancelled(self.state.drain(..).collect());
+ return KeymapResult::new(
+ // Note that Esc is not included here
+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
+ self.sticky(),
+ );
}
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),
+ None => Cow::Borrowed(&self.root),
};
let trie = match trie_node.search(&[*first]) {
- Some(KeyTrie::MappableCommand(ref cmd)) => {
- return KeymapResult::Matched(cmd.clone());
+ Some(KeyTrie::Leaf(ref cmd)) => {
+ return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky())
}
Some(KeyTrie::Sequence(ref cmds)) => {
- return KeymapResult::MatchedSequence(cmds.clone());
+ return KeymapResult::new(
+ KeymapResultKind::MatchedSequence(cmds.clone()),
+ self.sticky(),
+ )
}
- None => return KeymapResult::NotFound,
+ None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()),
Some(t) => t,
};
self.state.push(key);
match trie.search(&self.state[1..]) {
- Some(KeyTrie::Node(map)) => {
+ Some(&KeyTrie::Node(ref map)) => {
if map.is_sticky {
self.state.clear();
self.sticky = Some(map.clone());
}
- KeymapResult::Pending(map.clone())
+ KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
}
- Some(KeyTrie::MappableCommand(cmd)) => {
+ Some(&KeyTrie::Leaf(ref cmd)) => {
self.state.clear();
- KeymapResult::Matched(cmd.clone())
+ return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky());
}
- Some(KeyTrie::Sequence(cmds)) => {
+ Some(&KeyTrie::Sequence(ref cmds)) => {
self.state.clear();
- KeymapResult::MatchedSequence(cmds.clone())
+ KeymapResult::new(
+ KeymapResultKind::MatchedSequence(cmds.clone()),
+ self.sticky(),
+ )
}
- None => KeymapResult::Cancelled(self.state.drain(..).collect()),
+ None => KeymapResult::new(
+ KeymapResultKind::Cancelled(self.state.drain(..).collect()),
+ self.sticky(),
+ ),
}
}
+
+ pub fn merge(&mut self, other: Self) {
+ self.root.merge_nodes(other.root);
+ }
+}
+
+impl Deref for Keymap {
+ type Target = KeyTrieNode;
+
+ fn deref(&self) -> &Self::Target {
+ self.root.node().unwrap()
+ }
+}
+
+impl Default for Keymap {
+ fn default() -> Self {
+ Self::new(KeyTrie::Node(KeyTrieNode::default()))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(transparent)]
+pub struct Keymaps(pub HashMap<Mode, Keymap>);
+
+impl Keymaps {
+ /// Returns list of keys waiting to be disambiguated in current mode.
+ pub fn pending(&self) -> &[KeyEvent] {
+ self.0
+ .values()
+ .find_map(|keymap| match keymap.pending().is_empty() {
+ true => None,
+ false => Some(keymap.pending()),
+ })
+ .unwrap_or_default()
+ }
+}
+
+impl Deref for Keymaps {
+ type Target = HashMap<Mode, Keymap>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for Keymaps {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
}
impl Default for Keymaps {
fn default() -> Self {
- Self::new(Box::new(ArcSwap::new(Arc::new(default()))))
+ let normal = keymap!({ "Normal mode"
+ "h" | "left" => move_char_left,
+ "j" | "down" => move_line_down,
+ "k" | "up" => move_line_up,
+ "l" | "right" => move_char_right,
+
+ "t" => find_till_char,
+ "f" => find_next_char,
+ "T" => till_prev_char,
+ "F" => find_prev_char,
+ "r" => replace,
+ "R" => replace_with_yanked,
+ "A-." => repeat_last_motion,
+
+ "~" => switch_case,
+ "`" => switch_to_lowercase,
+ "A-`" => switch_to_uppercase,
+
+ "home" => goto_line_start,
+ "end" => goto_line_end,
+
+ "w" => move_next_word_start,
+ "b" => move_prev_word_start,
+ "e" => move_next_word_end,
+
+ "W" => move_next_long_word_start,
+ "B" => move_prev_long_word_start,
+ "E" => move_next_long_word_end,
+
+ "v" => select_mode,
+ "G" => goto_line,
+ "g" => { "Goto"
+ "g" => goto_file_start,
+ "e" => goto_last_line,
+ "f" => goto_file,
+ "h" => goto_line_start,
+ "l" => goto_line_end,
+ "s" => goto_first_nonwhitespace,
+ "d" => goto_definition,
+ "y" => goto_type_definition,
+ "r" => goto_reference,
+ "i" => goto_implementation,
+ "t" => goto_window_top,
+ "c" => goto_window_center,
+ "b" => goto_window_bottom,
+ "a" => goto_last_accessed_file,
+ "m" => goto_last_modified_file,
+ "n" => goto_next_buffer,
+ "p" => goto_previous_buffer,
+ "." => goto_last_modification,
+ },
+ ":" => command_mode,
+
+ "i" => insert_mode,
+ "I" => prepend_to_line,
+ "a" => append_mode,
+ "A" => append_to_line,
+ "o" => open_below,
+ "O" => open_above,
+
+ "d" => delete_selection,
+ "A-d" => delete_selection_noyank,
+ "c" => change_selection,
+ "A-c" => change_selection_noyank,
+
+ "C" => copy_selection_on_next_line,
+ "A-C" => copy_selection_on_prev_line,
+
+
+ "s" => select_regex,
+ "A-s" => split_selection_on_newline,
+ "S" => split_selection,
+ ";" => collapse_selection,
+ "A-;" => flip_selections,
+ "A-k" | "A-up" => expand_selection,
+ "A-j" | "A-down" => shrink_selection,
+ "A-h" | "A-left" => select_prev_sibling,
+ "A-l" | "A-right" => select_next_sibling,
+
+ "%" => select_all,
+ "x" => extend_line,
+ "X" => extend_to_line_bounds,
+ // crop_to_whole_line
+
+ "m" => { "Match"
+ "m" => match_brackets,
+ "s" => surround_add,
+ "r" => surround_replace,
+ "d" => surround_delete,
+ "a" => select_textobject_around,
+ "i" => select_textobject_inner,
+ },
+ "[" => { "Left bracket"
+ "d" => goto_prev_diag,
+ "D" => goto_first_diag,
+ "f" => goto_prev_function,
+ "c" => goto_prev_class,
+ "a" => goto_prev_parameter,
+ "o" => goto_prev_comment,
+ "space" => add_newline_above,
+ },
+ "]" => { "Right bracket"
+ "d" => goto_next_diag,
+ "D" => goto_last_diag,
+ "f" => goto_next_function,
+ "c" => goto_next_class,
+ "a" => goto_next_parameter,
+ "o" => goto_next_comment,
+ "space" => add_newline_below,
+ },
+
+ "/" => search,
+ "?" => rsearch,
+ "n" => search_next,
+ "N" => search_prev,
+ "*" => search_selection,
+
+ "u" => undo,
+ "U" => redo,
+ "A-u" => earlier,
+ "A-U" => later,
+
+ "y" => yank,
+ // yank_all
+ "p" => paste_after,
+ // paste_all
+ "P" => paste_before,
+
+ "Q" => record_macro,
+ "q" => replay_macro,
+
+ ">" => indent,
+ "<" => unindent,
+ "=" => format_selections,
+ "J" => join_selections,
+ "K" => keep_selections,
+ "A-K" => remove_selections,
+
+ "," => keep_primary_selection,
+ "A-," => remove_primary_selection,
+
+ // "q" => record_macro,
+ // "Q" => replay_macro,
+
+ "&" => align_selections,
+ "_" => trim_selections,
+
+ "(" => rotate_selections_backward,
+ ")" => rotate_selections_forward,
+ "A-(" => rotate_selection_contents_backward,
+ "A-)" => rotate_selection_contents_forward,
+
+ "A-:" => ensure_selections_forward,
+
+ "esc" => normal_mode,
+ "C-b" | "pageup" => page_up,
+ "C-f" | "pagedown" => page_down,
+ "C-u" => half_page_up,
+ "C-d" => half_page_down,
+
+ "C-w" => { "Window"
+ "C-w" | "w" => rotate_view,
+ "C-s" | "s" => hsplit,
+ "C-v" | "v" => vsplit,
+ "f" => goto_file_hsplit,
+ "F" => goto_file_vsplit,
+ "C-q" | "q" => wclose,
+ "C-o" | "o" => wonly,
+ "C-h" | "h" | "left" => jump_view_left,
+ "C-j" | "j" | "down" => jump_view_down,
+ "C-k" | "k" | "up" => jump_view_up,
+ "C-l" | "l" | "right" => jump_view_right,
+ "n" => { "New split scratch buffer"
+ "C-s" | "s" => hsplit_new,
+ "C-v" | "v" => vsplit_new,
+ },
+ },
+
+ // move under <space>c
+ "C-c" => toggle_comments,
+
+ // z family for save/restore/combine from/to sels from register
+
+ "tab" => jump_forward, // tab == <C-i>
+ "C-o" => jump_backward,
+ "C-s" => save_selection,
+
+ "space" => { "Space"
+ "f" => file_picker,
+ "b" => buffer_picker,
+ "s" => symbol_picker,
+ "S" => workspace_symbol_picker,
+ "a" => code_action,
+ "'" => last_picker,
+ "d" => { "Debug (experimental)" sticky=true
+ "l" => dap_launch,
+ "b" => dap_toggle_breakpoint,
+ "c" => dap_continue,
+ "h" => dap_pause,
+ "i" => dap_step_in,
+ "o" => dap_step_out,
+ "n" => dap_next,
+ "v" => dap_variables,
+ "t" => dap_terminate,
+ "C-c" => dap_edit_condition,
+ "C-l" => dap_edit_log,
+ "s" => { "Switch"
+ "t" => dap_switch_thread,
+ "f" => dap_switch_stack_frame,
+ // sl, sb
+ },
+ "e" => dap_enable_exceptions,
+ "E" => dap_disable_exceptions,
+ },
+ "w" => { "Window"
+ "C-w" | "w" => rotate_view,
+ "C-s" | "s" => hsplit,
+ "C-v" | "v" => vsplit,
+ "f" => goto_file_hsplit,
+ "F" => goto_file_vsplit,
+ "C-q" | "q" => wclose,
+ "C-o" | "o" => wonly,
+ "C-h" | "h" | "left" => jump_view_left,
+ "C-j" | "j" | "down" => jump_view_down,
+ "C-k" | "k" | "up" => jump_view_up,
+ "C-l" | "l" | "right" => jump_view_right,
+ "n" => { "New split scratch buffer"
+ "C-s" | "s" => hsplit_new,
+ "C-v" | "v" => vsplit_new,
+ },
+ },
+ "y" => yank_joined_to_clipboard,
+ "Y" => yank_main_selection_to_clipboard,
+ "p" => paste_clipboard_after,
+ "P" => paste_clipboard_before,
+ "R" => replace_selections_with_clipboard,
+ "/" => global_search,
+ "k" => hover,
+ "r" => rename_symbol,
+ "?" => command_palette,
+ },
+ "z" => { "View"
+ "z" | "c" => align_view_center,
+ "t" => align_view_top,
+ "b" => align_view_bottom,
+ "m" => align_view_middle,
+ "k" | "up" => scroll_up,
+ "j" | "down" => scroll_down,
+ "C-b" | "pageup" => page_up,
+ "C-f" | "pagedown" => page_down,
+ "C-u" => half_page_up,
+ "C-d" => half_page_down,
+ },
+ "Z" => { "View" sticky=true
+ "z" | "c" => align_view_center,
+ "t" => align_view_top,
+ "b" => align_view_bottom,
+ "m" => align_view_middle,
+ "k" | "up" => scroll_up,
+ "j" | "down" => scroll_down,
+ "C-b" | "pageup" => page_up,
+ "C-f" | "pagedown" => page_down,
+ "C-u" => half_page_up,
+ "C-d" => half_page_down,
+ },
+
+ "\"" => select_register,
+ "|" => shell_pipe,
+ "A-|" => shell_pipe_to,
+ "!" => shell_insert_output,
+ "A-!" => shell_append_output,
+ "$" => shell_keep_pipe,
+ "C-z" => suspend,
+
+ "C-a" => increment,
+ "C-x" => decrement,
+ });
+ let mut select = normal.clone();
+ select.merge_nodes(keymap!({ "Select mode"
+ "h" | "left" => extend_char_left,
+ "j" | "down" => extend_line_down,
+ "k" | "up" => extend_line_up,
+ "l" | "right" => extend_char_right,
+
+ "w" => extend_next_word_start,
+ "b" => extend_prev_word_start,
+ "e" => extend_next_word_end,
+ "W" => extend_next_long_word_start,
+ "B" => extend_prev_long_word_start,
+ "E" => extend_next_long_word_end,
+
+ "n" => extend_search_next,
+ "N" => extend_search_prev,
+
+ "t" => extend_till_char,
+ "f" => extend_next_char,
+ "T" => extend_till_prev_char,
+ "F" => extend_prev_char,
+
+ "home" => extend_to_line_start,
+ "end" => extend_to_line_end,
+ "esc" => exit_select_mode,
+
+ "v" => normal_mode,
+ }));
+ let insert = keymap!({ "Insert mode"
+ "esc" => normal_mode,
+
+ "backspace" => delete_char_backward,
+ "C-h" => delete_char_backward,
+ "del" => delete_char_forward,
+ "C-d" => delete_char_forward,
+ "ret" => insert_newline,
+ "C-j" => insert_newline,
+ "tab" => insert_tab,
+ "C-w" => delete_word_backward,
+ "A-backspace" => delete_word_backward,
+ "A-d" => delete_word_forward,
+
+ "left" => move_char_left,
+ "C-b" => move_char_left,
+ "down" => move_line_down,
+ "C-n" => move_line_down,
+ "up" => move_line_up,
+ "C-p" => move_line_up,
+ "right" => move_char_right,
+ "C-f" => move_char_right,
+ "A-b" => move_prev_word_end,
+ "A-left" => move_prev_word_end,
+ "A-f" => move_next_word_start,
+ "A-right" => move_next_word_start,
+ "A-<" => goto_file_start,
+ "A->" => goto_file_end,
+ "pageup" => page_up,
+ "pagedown" => page_down,
+ "home" => goto_line_start,
+ "C-a" => goto_line_start,
+ "end" => goto_line_end_newline,
+ "C-e" => goto_line_end_newline,
+
+ "C-k" => kill_to_line_end,
+ "C-u" => kill_to_line_start,
+
+ "C-x" => completion,
+ "C-r" => insert_register,
+ });
+ Self(hashmap!(
+ Mode::Normal => Keymap::new(normal),
+ Mode::Select => Keymap::new(select),
+ Mode::Insert => Keymap::new(insert),
+ ))
}
}
/// 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>) {
- for (mode, keys) in dst {
- keys.merge_nodes(
- delta
- .remove(mode)
- .unwrap_or_else(|| KeyTrie::Node(KeyTrieNode::default())),
- )
+pub fn merge_keys(mut config: Config) -> Config {
+ let mut delta = std::mem::take(&mut config.keys);
+ for (mode, keys) in &mut *config.keys {
+ keys.merge(delta.remove(mode).unwrap_or_default())
}
+ config
}
#[cfg(test)]
mod tests {
- use super::macros::keymap;
use super::*;
- use arc_swap::access::Constant;
- use helix_core::hashmap;
#[test]
#[should_panic]
@@ -422,108 +894,103 @@ 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,
- },
- })
+ let config = Config {
+ keys: Keymaps(hashmap! {
+ Mode::Normal => Keymap::new(
+ keymap!({ "Normal mode"
+ "i" => normal_mode,
+ "无" => insert_mode,
+ "z" => jump_backward,
+ "g" => { "Merge into goto mode"
+ "$" => goto_line_end,
+ "g" => delete_char_forward,
+ },
+ })
+ )
+ }),
+ ..Default::default()
};
- let mut merged_keyamp = default();
- merge_keys(&mut merged_keyamp, keymap.clone());
- assert_ne!(keymap, merged_keyamp);
+ let mut merged_config = merge_keys(config.clone());
+ assert_ne!(config, merged_config);
- let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone())));
+ let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
assert_eq!(
- keymap.get(Mode::Normal, key!('i')),
- KeymapResult::Matched(MappableCommand::normal_mode),
+ keymap.get(key!('i')).kind,
+ KeymapResultKind::Matched(MappableCommand::normal_mode),
"Leaf should replace leaf"
);
assert_eq!(
- keymap.get(Mode::Normal, key!('无')),
- KeymapResult::Matched(MappableCommand::insert_mode),
+ keymap.get(key!('无')).kind,
+ KeymapResultKind::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')),
- KeymapResult::Matched(MappableCommand::jump_backward),
+ keymap.get(key!('z')).kind,
+ KeymapResultKind::Matched(MappableCommand::jump_backward),
"Leaf should replace node"
);
-
- let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
- keymap.search(&[key!('g'), key!('$')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::goto_line_end),
+ keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
+ &KeyTrie::Leaf(MappableCommand::goto_line_end),
"Leaf should be present in merged subnode"
);
// Assumes that `gg` is in default keymap
assert_eq!(
- keymap.search(&[key!('g'), key!('g')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::delete_char_forward),
+ keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
+ &KeyTrie::Leaf(MappableCommand::delete_char_forward),
"Leaf should replace old leaf in merged subnode"
);
// Assumes that `ge` is in default keymap
assert_eq!(
- keymap.search(&[key!('g'), key!('e')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::goto_last_line),
+ keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
+ &KeyTrie::Leaf(MappableCommand::goto_last_line),
"Old leaves in subnode should be present in merged node"
);
- assert!(
- merged_keyamp
- .get(&Mode::Normal)
- .and_then(|key_trie| key_trie.node())
- .unwrap()
- .len()
- > 1
- );
- assert!(
- merged_keyamp
- .get(&Mode::Insert)
- .and_then(|key_trie| key_trie.node())
- .unwrap()
- .len()
- > 0
- );
+ assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
+ assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
}
#[test]
fn order_should_be_set() {
- let keymap = hashmap! {
- Mode::Normal => keymap!({ "Normal mode"
- "space" => { ""
- "s" => { ""
- "v" => vsplit,
- "c" => hsplit,
- },
- },
- })
+ let config = Config {
+ keys: Keymaps(hashmap! {
+ Mode::Normal => Keymap::new(
+ keymap!({ "Normal mode"
+ "space" => { ""
+ "s" => { ""
+ "v" => vsplit,
+ "c" => hsplit,
+ },
+ },
+ })
+ )
+ }),
+ ..Default::default()
};
- 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 mut merged_config = merge_keys(config.clone());
+ assert_ne!(config, merged_config);
+ let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
// Make sure mapping works
assert_eq!(
- keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(),
- &KeyTrie::MappableCommand(MappableCommand::vsplit),
+ keymap
+ .root()
+ .search(&[key!(' '), key!('s'), key!('v')])
+ .unwrap(),
+ &KeyTrie::Leaf(MappableCommand::vsplit),
"Leaf should be present in merged subnode"
);
// Make sure an order was set during merge
- let node = keymap.search(&[crate::key!(' ')]).unwrap();
- assert!(!node.node().unwrap().order.as_slice().is_empty())
+ let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
+ assert!(!node.node().unwrap().order().is_empty())
}
#[test]
fn aliased_modes_are_same_in_default_keymap() {
- let keymaps = Keymaps::default().map();
- let root = keymaps.get(&Mode::Normal).unwrap();
+ let keymaps = Keymaps::default();
+ let root = keymaps.get(&Mode::Normal).unwrap().root();
assert_eq!(
root.search(&[key!(' '), key!('w')]).unwrap(),
root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
@@ -546,7 +1013,7 @@ mod tests {
},
"j" | "k" => move_line_down,
});
- let keymap = normal_mode;
+ let keymap = Keymap::new(normal_mode);
let mut reverse_map = keymap.reverse_map();
// sort keybindings in order to have consistent tests
@@ -573,42 +1040,7 @@ mod tests {
vec![vec![key!('j')], vec![key!('k')]]
),
]),
- "Mismatch"
+ "Mistmatch"
)
}
-
- #[test]
- fn escaped_keymap() {
- use crate::commands::MappableCommand;
- use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
-
- let keys = r#"
-"+" = [
- "select_all",
- ":pipe sed -E 's/\\s+$//g'",
-]
- "#;
-
- let key = KeyEvent {
- code: KeyCode::Char('+'),
- modifiers: KeyModifiers::NONE,
- };
-
- let expectation = KeyTrie::Node(KeyTrieNode::new(
- "",
- hashmap! {
- key => KeyTrie::Sequence(vec!{
- MappableCommand::select_all,
- MappableCommand::Typable {
- name: "pipe".to_string(),
- args: "sed -E 's/\\s+$//g'".to_string(),
- doc: "".to_string(),
- },
- })
- },
- vec![key],
- ));
-
- assert_eq!(toml::from_str(keys), Ok(expectation));
- }
}