@ -1,7 +1,7 @@
pub use crate ::commands ::Command ;
pub use crate ::commands ::Command ;
use crate ::config ::Config ;
use crate ::config ::Config ;
use helix_core ::hashmap ;
use helix_core ::hashmap ;
use helix_view ::{ document ::Mode , in put::KeyEvent } ;
use helix_view ::{ document ::Mode , in fo::Info , in put::KeyEvent } ;
use serde ::Deserialize ;
use serde ::Deserialize ;
use std ::{
use std ::{
collections ::HashMap ,
collections ::HashMap ,
@ -24,30 +24,276 @@ macro_rules! key {
} ;
} ;
}
}
macro_rules! ctrl {
/// Macro for defining the root of a `Keymap` object. Example:
( $( $ch :tt ) * ) = > {
///
KeyEvent {
/// ```
code : ::helix_view ::keyboard ::KeyCode ::Char ( $( $ch ) * ) ,
/// # use helix_core::hashmap;
modifiers : ::helix_view ::keyboard ::KeyModifiers ::CONTROL ,
/// # 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 ::Command ::$cmd )
} ;
( @ trie
{ $label :literal $( $( $key :literal ) | + = > $value :tt , ) + }
) = > {
keymap ! ( { $label $( $( $key ) | + = > $value , ) + } )
} ;
(
{ $label :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 ( ) ;
_map . insert (
_key ,
keymap ! ( @ trie $value )
) ;
_order . push ( _key ) ;
) +
) *
$crate ::keymap ::KeyTrie ::Node ( $crate ::keymap ::KeyTrieNode ::new ( $label , _map , _order ) )
}
}
} ;
} ;
}
}
macro_rules! alt {
#[ derive(Debug, Clone, Deserialize) ]
( $( $ch :tt ) * ) = > {
pub struct KeyTrieNode {
KeyEvent {
/// A label for keys coming under this node, like "Goto mode"
code : ::helix_view ::keyboard ::KeyCode ::Char ( $( $ch ) * ) ,
#[ serde(skip) ]
modifiers : ::helix_view ::keyboard ::KeyModifiers ::ALT ,
name : String ,
#[ serde(flatten) ]
map : HashMap < KeyEvent , KeyTrie > ,
#[ serde(skip) ]
order : Vec < KeyEvent > ,
}
impl KeyTrieNode {
pub fn new ( name : & str , map : HashMap < KeyEvent , KeyTrie > , order : Vec < KeyEvent > ) -> Self {
Self {
name : name . to_string ( ) ,
map ,
order ,
}
}
} ;
}
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.
pub fn merge ( & mut self , mut other : Self ) {
for ( key , trie ) in std ::mem ::take ( & mut other . map ) {
if let Some ( KeyTrie ::Node ( node ) ) = self . map . get_mut ( & key ) {
if let KeyTrie ::Node ( other_node ) = trie {
node . merge ( other_node ) ;
continue ;
}
}
self . map . insert ( key , trie ) ;
}
for & key in self . map . keys ( ) {
if ! self . order . contains ( & key ) {
self . order . push ( key ) ;
}
}
}
}
impl From < KeyTrieNode > for Info {
fn from ( node : KeyTrieNode ) -> Self {
let mut body : Vec < ( & str , Vec < KeyEvent > ) > = Vec ::with_capacity ( node . len ( ) ) ;
for ( & key , trie ) in node . iter ( ) {
let desc = match trie {
KeyTrie ::Leaf ( cmd ) = > cmd . doc ( ) ,
KeyTrie ::Node ( n ) = > n . name ( ) ,
} ;
match body . iter ( ) . position ( | ( d , _ ) | d = = & desc ) {
// FIXME: multiple keys are ordered randomly (use BTreeSet)
Some ( pos ) = > body [ pos ] . 1. push ( key ) ,
None = > body . push ( ( desc , vec! [ key ] ) ) ,
}
}
body . sort_unstable_by_key ( | ( _ , keys ) | {
node . order . iter ( ) . position ( | & k | k = = keys [ 0 ] ) . unwrap ( )
} ) ;
let prefix = format! ( "{} " , node . 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 ::key ( node . name ( ) , body )
}
}
impl Default for KeyTrieNode {
fn default ( ) -> Self {
Self ::new ( "" , HashMap ::new ( ) , Vec ::new ( ) )
}
}
impl PartialEq for KeyTrieNode {
fn eq ( & self , other : & Self ) -> bool {
self . map = = other . map
}
}
impl Deref for KeyTrieNode {
type Target = HashMap < KeyEvent , KeyTrie > ;
fn deref ( & self ) -> & Self ::Target {
& self . map
}
}
impl DerefMut for KeyTrieNode {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . map
}
}
#[ derive(Debug, Clone, PartialEq, Deserialize) ]
#[ serde(untagged) ]
pub enum KeyTrie {
Leaf ( Command ) ,
Node ( KeyTrieNode ) ,
}
impl KeyTrie {
pub fn node ( & self ) -> Option < & KeyTrieNode > {
match * self {
KeyTrie ::Node ( ref node ) = > Some ( node ) ,
KeyTrie ::Leaf ( _ ) = > None ,
}
}
pub fn node_mut ( & mut self ) -> Option < & mut KeyTrieNode > {
match * self {
KeyTrie ::Node ( ref mut node ) = > Some ( node ) ,
KeyTrie ::Leaf ( _ ) = > None ,
}
}
/// Merge another KeyTrie in, assuming that this KeyTrie and the other
/// are both Nodes. Panics otherwise.
pub fn merge_nodes ( & mut self , mut other : Self ) {
let node = std ::mem ::take ( other . node_mut ( ) . unwrap ( ) ) ;
self . node_mut ( ) . unwrap ( ) . merge ( node ) ;
}
pub fn search ( & self , keys : & [ KeyEvent ] ) -> Option < & KeyTrie > {
let mut trie = self ;
for key in keys {
trie = match trie {
KeyTrie ::Node ( map ) = > map . get ( key ) ,
// leaf encountered while keys left to process
KeyTrie ::Leaf ( _ ) = > None ,
} ?
}
Some ( trie )
}
}
#[ derive(Debug, Clone, PartialEq) ]
pub enum KeymapResult {
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
Pending ( KeyTrieNode ) ,
Matched ( Command ) ,
/// Key was not found in the root keymap
NotFound ,
/// Key is invalid in combination with previous keys. Contains keys leading upto
/// and including current (invalid) key.
Cancelled ( Vec < KeyEvent > ) ,
}
#[ derive(Debug, Clone, PartialEq, Deserialize) ]
pub struct Keymap {
/// Always a Node
#[ serde(flatten) ]
root : KeyTrie ,
#[ serde(skip) ]
state : Vec < KeyEvent > ,
}
impl Keymap {
pub fn new ( root : KeyTrie ) -> Self {
Keymap {
root ,
state : Vec ::new ( ) ,
}
}
pub fn root ( & self ) -> & KeyTrie {
& self . root
}
/// Lookup `key` in the keymap to try and find a command to execute
pub fn get ( & mut self , key : KeyEvent ) -> KeymapResult {
let & first = self . state . get ( 0 ) . unwrap_or ( & key ) ;
let trie = match self . root . search ( & [ first ] ) {
Some ( & KeyTrie ::Leaf ( cmd ) ) = > return KeymapResult ::Matched ( cmd ) ,
None = > return KeymapResult ::NotFound ,
Some ( t ) = > t ,
} ;
self . state . push ( key ) ;
match trie . search ( & self . state [ 1 .. ] ) {
Some ( & KeyTrie ::Node ( ref map ) ) = > KeymapResult ::Pending ( map . clone ( ) ) ,
Some ( & KeyTrie ::Leaf ( command ) ) = > {
self . state . clear ( ) ;
KeymapResult ::Matched ( command )
}
None = > KeymapResult ::Cancelled ( self . state . drain ( .. ) . collect ( ) ) ,
}
}
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) ]
#[ derive(Debug, Clone, PartialEq, Deserialize) ]
#[ serde(transparent) ]
#[ serde(transparent) ]
pub struct Keymaps ( pub HashMap < Mode , HashMap < KeyEvent , Command > > ) ;
pub struct Keymaps ( pub HashMap < Mode , Keymap > ) ;
impl Deref for Keymaps {
impl Deref for Keymaps {
type Target = HashMap < Mode , HashMap < KeyEvent , Command > > ;
type Target = HashMap < Mode , Keymap > ;
fn deref ( & self ) -> & Self ::Target {
fn deref ( & self ) -> & Self ::Target {
& self . 0
& self . 0
@ -62,252 +308,298 @@ impl DerefMut for Keymaps {
impl Default for Keymaps {
impl Default for Keymaps {
fn default ( ) -> Keymaps {
fn default ( ) -> Keymaps {
let normal = hashmap ! (
let normal = keymap ! ( { "Normal mode"
key ! ( 'h' ) = > Command ::move_char_left ,
"h" | "left" = > move_char_left ,
key ! ( 'j' ) = > Command ::move_line_down ,
"j" | "down" = > move_line_down ,
key ! ( 'k' ) = > Command ::move_line_up ,
"k" | "up" = > move_line_up ,
key ! ( 'l' ) = > Command ::move_char_right ,
"l" | "right" = > move_char_right ,
key ! ( Left ) = > Command ::move_char_left ,
"t" = > find_till_char ,
key ! ( Down ) = > Command ::move_line_down ,
"f" = > find_next_char ,
key ! ( Up ) = > Command ::move_line_up ,
"T" = > till_prev_char ,
key ! ( Right ) = > Command ::move_char_right ,
"F" = > find_prev_char ,
"r" = > replace ,
key ! ( 't' ) = > Command ::find_till_char ,
"R" = > replace_with_yanked ,
key ! ( 'f' ) = > Command ::find_next_char ,
key ! ( 'T' ) = > Command ::till_prev_char ,
"~" = > switch_case ,
key ! ( 'F' ) = > Command ::find_prev_char ,
"`" = > switch_to_lowercase ,
// and matching set for select mode (extend)
"A-`" = > switch_to_uppercase ,
//
key ! ( 'r' ) = > Command ::replace ,
"home" = > goto_line_start ,
key ! ( 'R' ) = > Command ::replace_with_yanked ,
"end" = > goto_line_end ,
key ! ( '~' ) = > Command ::switch_case ,
"w" = > move_next_word_start ,
alt ! ( '`' ) = > Command ::switch_to_uppercase ,
"b" = > move_prev_word_start ,
key ! ( '`' ) = > Command ::switch_to_lowercase ,
"e" = > move_next_word_end ,
key ! ( Home ) = > Command ::goto_line_start ,
"W" = > move_next_long_word_start ,
key ! ( End ) = > Command ::goto_line_end ,
"B" = > move_prev_long_word_start ,
"E" = > move_next_long_word_end ,
key ! ( 'w' ) = > Command ::move_next_word_start ,
key ! ( 'b' ) = > Command ::move_prev_word_start ,
"v" = > select_mode ,
key ! ( 'e' ) = > Command ::move_next_word_end ,
"g" = > { "Goto"
"g" = > goto_file_start ,
key ! ( 'W' ) = > Command ::move_next_long_word_start ,
"e" = > goto_file_end ,
key ! ( 'B' ) = > Command ::move_prev_long_word_start ,
"h" = > goto_line_start ,
key ! ( 'E' ) = > Command ::move_next_long_word_end ,
"l" = > goto_line_end ,
"s" = > goto_first_nonwhitespace ,
key ! ( 'v' ) = > Command ::select_mode ,
"d" = > goto_definition ,
key ! ( 'g' ) = > Command ::goto_mode ,
"y" = > goto_type_definition ,
key ! ( ':' ) = > Command ::command_mode ,
"r" = > goto_reference ,
"i" = > goto_implementation ,
key ! ( 'i' ) = > Command ::insert_mode ,
"t" = > goto_window_top ,
key ! ( 'I' ) = > Command ::prepend_to_line ,
"m" = > goto_window_middle ,
key ! ( 'a' ) = > Command ::append_mode ,
"b" = > goto_window_bottom ,
key ! ( 'A' ) = > Command ::append_to_line ,
"a" = > goto_last_accessed_file ,
key ! ( 'o' ) = > Command ::open_below ,
} ,
key ! ( 'O' ) = > Command ::open_above ,
":" = > command_mode ,
"i" = > insert_mode ,
"I" = > prepend_to_line ,
"a" = > append_mode ,
"A" = > append_to_line ,
"o" = > open_below ,
"O" = > open_above ,
// [<space> ]<space> equivalents too (add blank new line, no edit)
// [<space> ]<space> equivalents too (add blank new line, no edit)
"d" = > delete_selection ,
key ! ( 'd' ) = > Command ::delete_selection ,
// TODO: also delete without yanking
// TODO: also delete without yanking
key ! ( 'c' ) = > Command ::change_selection ,
"c" = > change_selection ,
// TODO: also change delete without yanking
// TODO: also change delete without yanking
// key!('r') => Command::replace_with_char,
"s" = > select_regex ,
"A-s" = > split_selection_on_newline ,
key ! ( 's' ) = > Command ::select_regex ,
"S" = > split_selection ,
alt ! ( 's' ) = > Command ::split_selection_on_newline ,
";" = > collapse_selection ,
key ! ( 'S' ) = > Command ::split_selection ,
"A-;" = > flip_selections ,
key ! ( ';' ) = > Command ::collapse_selection ,
"%" = > select_all ,
alt ! ( ';' ) = > Command ::flip_selections ,
"x" = > extend_line ,
key ! ( '%' ) = > Command ::select_all ,
"X" = > extend_to_line_bounds ,
key ! ( 'x' ) = > Command ::extend_line ,
key ! ( 'X' ) = > Command ::extend_to_line_bounds ,
// crop_to_whole_line
// 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 ,
} ,
"]" = > { "Right bracket"
"d" = > goto_next_diag ,
"D" = > goto_last_diag ,
} ,
key ! ( 'm' ) = > Command ::match_mode ,
"/" = > search ,
key ! ( '[' ) = > Command ::left_bracket_mode ,
key ! ( ']' ) = > Command ::right_bracket_mode ,
key ! ( '/' ) = > Command ::search ,
// ? for search_reverse
// ? for search_reverse
key ! ( 'n' ) = > Command ::search_next ,
"n" = > search_next ,
key ! ( 'N' ) = > Command ::extend_search_next ,
"N" = > extend_search_next ,
// N for search_prev
// N for search_prev
key ! ( '*' ) = > Command ::search_selection ,
"*" = > search_selection ,
key ! ( 'u' ) = > Command ::undo ,
"u" = > undo ,
key ! ( 'U' ) = > Command ::redo ,
"U" = > redo ,
key ! ( 'y' ) = > Command ::yank ,
"y" = > yank ,
// yank_all
// yank_all
key ! ( 'p' ) = > Command ::paste_after ,
"p" = > paste_after ,
// paste_all
// paste_all
key ! ( 'P' ) = > Command ::paste_before ,
"P" = > paste_before ,
key ! ( '>' ) = > Command ::indent ,
">" = > indent ,
key ! ( '<' ) = > Command ::unindent ,
"<" = > unindent ,
key ! ( '=' ) = > Command ::format_selections ,
"=" = > format_selections ,
key ! ( 'J' ) = > Command ::join_selections ,
"J" = > join_selections ,
// TODO: conflicts hover/doc
// TODO: conflicts hover/doc
key ! ( 'K' ) = > Command ::keep_selections ,
"K" = > keep_selections ,
// TODO: and another method for inverse
// TODO: and another method for inverse
// TODO: clashes with space mode
// TODO: clashes with space mode
key ! ( ' ' ) = > Command ::keep_primary_selection ,
"space" = > keep_primary_selection ,
// key!('q') => Command:: record_macro,
// "q" => record_macro,
// key!('Q') => Command:: replay_macro,
// "Q" => replay_macro,
// ~ / apostrophe => change case
// & align selections
// & align selections
// _ trim selections
// _ trim selections
// C / altC = copy (repeat) selections on prev/next lines
// C / altC = copy (repeat) selections on prev/next lines
key ! ( Esc ) = > Command ::normal_mode ,
"esc" = > normal_mode ,
key ! ( PageUp ) = > Command ::page_up ,
"C-b" | "pageup" = > page_up ,
key ! ( PageDown ) = > Command ::page_down ,
"C-f" | "pagedown" = > page_down ,
ctrl ! ( 'b' ) = > Command ::page_up ,
"C-u" = > half_page_up ,
ctrl ! ( 'f' ) = > Command ::page_down ,
"C-d" = > half_page_down ,
ctrl ! ( 'u' ) = > Command ::half_page_up ,
ctrl ! ( 'd' ) = > Command ::half_page_down ,
"C-w" = > { "Window"
"C-w" | "w" = > rotate_view ,
ctrl ! ( 'w' ) = > Command ::window_mode ,
"C-h" | "h" = > hsplit ,
"C-v" | "v" = > vsplit ,
"C-q" | "q" = > wclose ,
} ,
// move under <space>c
// move under <space>c
ctrl ! ( 'c' ) = > Command ::toggle_comments ,
"C-c" = > toggle_comments ,
key ! ( 'K' ) = > Command ::hover ,
"K" = > hover ,
// z family for save/restore/combine from/to sels from register
// z family for save/restore/combine from/to sels from register
// supposedly ctrl!('i') but did not work
// supposedly "C-i" but did not work
key ! ( Tab ) = > Command ::jump_forward ,
"tab" = > jump_forward ,
ctrl ! ( 'o' ) = > Command ::jump_backward ,
"C-o" = > jump_backward ,
// ctrl!('s') => Command::save_selection,
// "C-s" => save_selection,
key ! ( ' ' ) = > Command ::space_mode ,
"space" = > { "Space"
key ! ( 'z' ) = > Command ::view_mode ,
"f" = > file_picker ,
"b" = > buffer_picker ,
"s" = > symbol_picker ,
"a" = > code_action ,
"'" = > last_picker ,
"w" = > { "Window"
"C-w" | "w" = > rotate_view ,
"C-h" | "h" = > hsplit ,
"C-v" | "v" = > vsplit ,
"C-q" | "q" = > wclose ,
} ,
"y" = > yank_joined_to_clipboard ,
"Y" = > yank_main_selection_to_clipboard ,
"p" = > paste_clipboard_after ,
"P" = > paste_clipboard_before ,
"R" = > replace_selections_with_clipboard ,
"space" = > keep_primary_selection ,
} ,
"z" = > { "View"
"z" | "c" = > align_view_center ,
"t" = > align_view_top ,
"b" = > align_view_bottom ,
"m" = > align_view_middle ,
"k" = > scroll_up ,
"j" = > scroll_down ,
} ,
key ! ( '"' ) = > Command ::select_register ,
"\"" = > select_register ,
) ;
} );
// TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
// TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
// we keep this separate select mode. More keys can fit into normal mode then, but it's weird
// we keep this separate select mode. More keys can fit into normal mode then, but it's weird
// because some selection operations can now be done from normal mode, some from select mode.
// because some selection operations can now be done from normal mode, some from select mode.
let mut select = normal . clone ( ) ;
let mut select = normal . clone ( ) ;
select . extend (
select . merge_nodes ( keymap ! ( { "Select mode"
hashmap ! (
"h" | "left" = > extend_char_left ,
key ! ( 'h' ) = > Command ::extend_char_left ,
"j" | "down" = > extend_line_down ,
key ! ( 'j' ) = > Command ::extend_line_down ,
"k" | "up" = > extend_line_up ,
key ! ( 'k' ) = > Command ::extend_line_up ,
"l" | "right" = > extend_char_right ,
key ! ( 'l' ) = > Command ::extend_char_right ,
"w" = > extend_next_word_start ,
key ! ( Left ) = > Command ::extend_char_left ,
"b" = > extend_prev_word_start ,
key ! ( Down ) = > Command ::extend_line_down ,
"e" = > extend_next_word_end ,
key ! ( Up ) = > Command ::extend_line_up ,
key ! ( Right ) = > Command ::extend_char_right ,
"t" = > extend_till_char ,
"f" = > extend_next_char ,
key ! ( 'w' ) = > Command ::extend_next_word_start ,
"T" = > extend_till_prev_char ,
key ! ( 'b' ) = > Command ::extend_prev_word_start ,
"F" = > extend_prev_char ,
key ! ( 'e' ) = > Command ::extend_next_word_end ,
"home" = > goto_line_start ,
key ! ( 't' ) = > Command ::extend_till_char ,
"end" = > goto_line_end ,
key ! ( 'f' ) = > Command ::extend_next_char ,
"esc" = > exit_select_mode ,
} ) ) ;
key ! ( 'T' ) = > Command ::extend_till_prev_char ,
let insert = keymap ! ( { "Insert mode"
key ! ( 'F' ) = > Command ::extend_prev_char ,
"esc" = > normal_mode ,
key ! ( Home ) = > Command ::goto_line_start ,
key ! ( End ) = > Command ::goto_line_end ,
"backspace" = > delete_char_backward ,
key ! ( Esc ) = > Command ::exit_select_mode ,
"del" = > delete_char_forward ,
)
"ret" = > insert_newline ,
. into_iter ( ) ,
"tab" = > insert_tab ,
) ;
"C-w" = > delete_word_backward ,
"left" = > move_char_left ,
"down" = > move_line_down ,
"up" = > move_line_up ,
"right" = > move_char_right ,
"pageup" = > page_up ,
"pagedown" = > page_down ,
"home" = > goto_line_start ,
"end" = > goto_line_end_newline ,
"C-x" = > completion ,
} ) ;
Keymaps ( hashmap ! (
Keymaps ( hashmap ! (
// as long as you cast the first item, rust is able to infer the other cases
Mode ::Normal = > Keymap ::new ( normal ) ,
// TODO: select could be normal mode with some bindings merged over
Mode ::Select = > Keymap ::new ( select ) ,
Mode ::Normal = > normal ,
Mode ::Insert = > Keymap ::new ( insert ) ,
Mode ::Select = > select ,
Mode ::Insert = > hashmap ! (
key ! ( Esc ) = > Command ::normal_mode as Command ,
key ! ( Backspace ) = > Command ::delete_char_backward ,
key ! ( Delete ) = > Command ::delete_char_forward ,
key ! ( Enter ) = > Command ::insert_newline ,
key ! ( Tab ) = > Command ::insert_tab ,
key ! ( Left ) = > Command ::move_char_left ,
key ! ( Down ) = > Command ::move_line_down ,
key ! ( Up ) = > Command ::move_line_up ,
key ! ( Right ) = > Command ::move_char_right ,
key ! ( PageUp ) = > Command ::page_up ,
key ! ( PageDown ) = > Command ::page_down ,
key ! ( Home ) = > Command ::goto_line_start ,
key ! ( End ) = > Command ::goto_line_end_newline ,
ctrl ! ( 'x' ) = > Command ::completion ,
ctrl ! ( 'w' ) = > Command ::delete_word_backward ,
) ,
) )
) )
}
}
}
}
/// Merge default config keys with user overwritten keys for custom
/// Merge default config keys with user overwritten keys for custom user config.
/// user config.
pub fn merge_keys ( mut config : Config ) -> Config {
pub fn merge_keys ( mut config : Config ) -> Config {
let mut delta = std ::mem ::take ( & mut config . keys ) ;
let mut delta = std ::mem ::take ( & mut config . keys ) ;
for ( mode , keys ) in & mut * config . keys {
for ( mode , keys ) in & mut * config . keys {
keys . extend ( delta . remove ( mode ) . unwrap_or_default ( ) ) ;
keys . merge ( delta . remove ( mode ) . unwrap_or_default ( ) )
}
}
config
config
}
}
#[ test ]
#[ test ]
fn merge_partial_keys ( ) {
fn merge_partial_keys ( ) {
use helix_view ::keyboard ::{ KeyCode , KeyModifiers } ;
let config = Config {
let config = Config {
keys : Keymaps ( hashmap ! {
keys : Keymaps ( hashmap ! {
Mode ::Normal = > hashmap ! {
Mode ::Normal = > Keymap ::new (
KeyEvent {
keymap ! ( { "Normal mode"
code : KeyCode ::Char ( 'i' ) ,
"i" = > normal_mode ,
modifiers : KeyModifiers ::NONE ,
"无" = > insert_mode ,
} = > Command ::normal_mode ,
"z" = > jump_backward ,
KeyEvent { // key that does not exist
"g" = > { "Merge into goto mode"
code : KeyCode ::Char ( '无' ) ,
"$" = > goto_line_end ,
modifiers : KeyModifiers ::NONE ,
"g" = > delete_char_forward ,
} = > Command ::insert_mode ,
} ,
} ,
} )
)
} ) ,
} ) ,
.. Default ::default ( )
.. Default ::default ( )
} ;
} ;
let merged_config = merge_keys ( config . clone ( ) ) ;
let mut merged_config = merge_keys ( config . clone ( ) ) ;
assert_ne! ( config , merged_config ) ;
assert_ne! ( config , merged_config ) ;
let keymap = merged_config . keys . 0. get_mut ( & Mode ::Normal ) . unwrap ( ) ;
assert_eq! (
assert_eq! (
* merged_config
keymap . get ( key ! ( 'i' ) ) ,
. keys
KeymapResult ::Matched ( Command ::normal_mode ) ,
. 0
"Leaf should replace leaf"
. get ( & Mode ::Normal )
. unwrap ( )
. get ( & KeyEvent {
code : KeyCode ::Char ( 'i' ) ,
modifiers : KeyModifiers ::NONE
} )
. unwrap ( ) ,
Command ::normal_mode
) ;
) ;
assert_eq! (
assert_eq! (
* merged_config
keymap . get ( key ! ( '无' ) ) ,
. keys
KeymapResult ::Matched ( Command ::insert_mode ) ,
. 0
"New leaf should be present in merged keymap"
. get ( & Mode ::Normal )
. unwrap ( )
. get ( & KeyEvent {
code : KeyCode ::Char ( '无' ) ,
modifiers : KeyModifiers ::NONE
} )
. unwrap ( ) ,
Command ::insert_mode
) ;
) ;
// Assumes that z is a node in the default keymap
assert_eq! (
keymap . get ( key ! ( 'z' ) ) ,
KeymapResult ::Matched ( Command ::jump_backward ) ,
"Leaf should replace node"
) ;
// Assumes that `g` is a node in default keymap
assert_eq! (
keymap . root ( ) . search ( & [ key ! ( 'g' ) , key ! ( '$' ) ] ) . unwrap ( ) ,
& KeyTrie ::Leaf ( Command ::goto_line_end ) ,
"Leaf should be present in merged subnode"
) ;
// Assumes that `gg` is in default keymap
assert_eq! (
keymap . root ( ) . search ( & [ key ! ( 'g' ) , key ! ( 'g' ) ] ) . unwrap ( ) ,
& KeyTrie ::Leaf ( Command ::delete_char_forward ) ,
"Leaf should replace old leaf in merged subnode"
) ;
// Assumes that `ge` is in default keymap
assert_eq! (
keymap . root ( ) . search ( & [ key ! ( 'g' ) , key ! ( 'e' ) ] ) . unwrap ( ) ,
& KeyTrie ::Leaf ( Command ::goto_file_end ) ,
"Old leaves in subnode should be present in merged node"
) ;
assert! ( merged_config . keys . 0. get ( & Mode ::Normal ) . unwrap ( ) . len ( ) > 1 ) ;
assert! ( merged_config . keys . 0. get ( & Mode ::Normal ) . unwrap ( ) . len ( ) > 1 ) ;
assert! ( merged_config . keys . 0. get ( & Mode ::Insert ) . unwrap ( ) . len ( ) > 0 ) ;
assert! ( merged_config . keys . 0. get ( & Mode ::Insert ) . unwrap ( ) . len ( ) > 0 ) ;
}
}