@ -1,7 +1,14 @@
use crate ::commands ::{ self , Command } ;
use crate ::commands ;
pub use crate ::commands ::Command ;
use anyhow ::{ anyhow , Error , Result } ;
use helix_core ::hashmap ;
use helix_view ::document ::Mode ;
use std ::collections ::HashMap ;
use std ::{
collections ::HashMap ,
fmt ::Display ,
ops ::{ Deref , DerefMut } ,
str ::FromStr ,
} ;
// Kakoune-inspired:
// mode = {
@ -95,8 +102,10 @@ use std::collections::HashMap;
// #[cfg(feature = "term")]
pub use crossterm ::event ::{ KeyCode , KeyEvent , KeyModifiers } ;
pub type Keymap = HashMap < KeyEvent , Command > ;
pub type Keymaps = HashMap < Mode , Keymap > ;
#[ derive(Clone, Debug) ]
pub struct Keymap ( pub HashMap < KeyEvent , Command > ) ;
#[ derive(Clone, Debug) ]
pub struct Keymaps ( pub HashMap < Mode , Keymap > ) ;
#[ macro_export ]
macro_rules! key {
@ -132,188 +141,491 @@ macro_rules! alt {
} ;
}
pub fn default ( ) -> Keymaps {
let normal = hashmap ! (
key ! ( 'h' ) = > commands ::move_char_left as Command ,
key ! ( 'j' ) = > commands ::move_line_down ,
key ! ( 'k' ) = > commands ::move_line_up ,
key ! ( 'l' ) = > commands ::move_char_right ,
key ! ( Left ) = > commands ::move_char_left ,
key ! ( Down ) = > commands ::move_line_down ,
key ! ( Up ) = > commands ::move_line_up ,
key ! ( Right ) = > commands ::move_char_right ,
key ! ( 't' ) = > commands ::find_till_char ,
key ! ( 'f' ) = > commands ::find_next_char ,
key ! ( 'T' ) = > commands ::till_prev_char ,
key ! ( 'F' ) = > commands ::find_prev_char ,
// and matching set for select mode (extend)
//
key ! ( 'r' ) = > commands ::replace ,
key ! ( 'R' ) = > commands ::replace_with_yanked ,
key ! ( Home ) = > commands ::move_line_start ,
key ! ( End ) = > commands ::move_line_end ,
key ! ( 'w' ) = > commands ::move_next_word_start ,
key ! ( 'b' ) = > commands ::move_prev_word_start ,
key ! ( 'e' ) = > commands ::move_next_word_end ,
key ! ( 'v' ) = > commands ::select_mode ,
key ! ( 'g' ) = > commands ::goto_mode ,
key ! ( ':' ) = > commands ::command_mode ,
key ! ( 'i' ) = > commands ::insert_mode ,
key ! ( 'I' ) = > commands ::prepend_to_line ,
key ! ( 'a' ) = > commands ::append_mode ,
key ! ( 'A' ) = > commands ::append_to_line ,
key ! ( 'o' ) = > commands ::open_below ,
key ! ( 'O' ) = > commands ::open_above ,
// [<space> ]<space> equivalents too (add blank new line, no edit)
key ! ( 'd' ) = > commands ::delete_selection ,
// TODO: also delete without yanking
key ! ( 'c' ) = > commands ::change_selection ,
// TODO: also change delete without yanking
// key!('r') => commands::replace_with_char,
key ! ( 's' ) = > commands ::select_regex ,
alt ! ( 's' ) = > commands ::split_selection_on_newline ,
key ! ( 'S' ) = > commands ::split_selection ,
key ! ( ';' ) = > commands ::collapse_selection ,
alt ! ( ';' ) = > commands ::flip_selections ,
key ! ( '%' ) = > commands ::select_all ,
key ! ( 'x' ) = > commands ::select_line ,
key ! ( 'X' ) = > commands ::extend_line ,
// or select mode X?
// extend_to_whole_line, crop_to_whole_line
key ! ( 'm' ) = > commands ::match_brackets ,
// TODO: refactor into
// key!('m') => commands::select_to_matching,
// key!('M') => commands::back_select_to_matching,
// select mode extend equivalents
// key!('.') => commands::repeat_insert,
// repeat_select
// TODO: figure out what key to use
// key!('[') => commands::expand_selection, ??
key ! ( '[' ) = > commands ::left_bracket_mode ,
key ! ( ']' ) = > commands ::right_bracket_mode ,
key ! ( '/' ) = > commands ::search ,
// ? for search_reverse
key ! ( 'n' ) = > commands ::search_next ,
key ! ( 'N' ) = > commands ::extend_search_next ,
// N for search_prev
key ! ( '*' ) = > commands ::search_selection ,
key ! ( 'u' ) = > commands ::undo ,
key ! ( 'U' ) = > commands ::redo ,
key ! ( 'y' ) = > commands ::yank ,
// yank_all
key ! ( 'p' ) = > commands ::paste_after ,
// paste_all
key ! ( 'P' ) = > commands ::paste_before ,
key ! ( '>' ) = > commands ::indent ,
key ! ( '<' ) = > commands ::unindent ,
key ! ( '=' ) = > commands ::format_selections ,
key ! ( 'J' ) = > commands ::join_selections ,
// TODO: conflicts hover/doc
key ! ( 'K' ) = > commands ::keep_selections ,
// TODO: and another method for inverse
// TODO: clashes with space mode
key ! ( ' ' ) = > commands ::keep_primary_selection ,
// key!('q') => commands::record_macro,
// key!('Q') => commands::replay_macro,
// ~ / apostrophe => change case
// & align selections
// _ trim selections
// C / altC = copy (repeat) selections on prev/next lines
key ! ( Esc ) = > commands ::normal_mode ,
key ! ( PageUp ) = > commands ::page_up ,
key ! ( PageDown ) = > commands ::page_down ,
ctrl ! ( 'b' ) = > commands ::page_up ,
ctrl ! ( 'f' ) = > commands ::page_down ,
ctrl ! ( 'u' ) = > commands ::half_page_up ,
ctrl ! ( 'd' ) = > commands ::half_page_down ,
ctrl ! ( 'w' ) = > commands ::window_mode ,
// move under <space>c
ctrl ! ( 'c' ) = > commands ::toggle_comments ,
key ! ( 'K' ) = > commands ::hover ,
// z family for save/restore/combine from/to sels from register
// supposedly ctrl!('i') but did not work
key ! ( Tab ) = > commands ::jump_forward ,
ctrl ! ( 'o' ) = > commands ::jump_backward ,
// ctrl!('s') => commands::save_selection,
key ! ( ' ' ) = > commands ::space_mode ,
key ! ( 'z' ) = > commands ::view_mode ,
key ! ( '"' ) = > commands ::select_register ,
) ;
// 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
// because some selection operations can now be done from normal mode, some from select mode.
let mut select = normal . clone ( ) ;
select . extend (
hashmap ! (
key ! ( 'h' ) = > commands ::extend_char_left as Command ,
key ! ( 'j' ) = > commands ::extend_line_down ,
key ! ( 'k' ) = > commands ::extend_line_up ,
key ! ( 'l' ) = > commands ::extend_char_right ,
key ! ( Left ) = > commands ::extend_char_left ,
key ! ( Down ) = > commands ::extend_line_down ,
key ! ( Up ) = > commands ::extend_line_up ,
key ! ( Right ) = > commands ::extend_char_right ,
key ! ( 'w' ) = > commands ::extend_next_word_start ,
key ! ( 'b' ) = > commands ::extend_prev_word_start ,
key ! ( 'e' ) = > commands ::extend_next_word_end ,
key ! ( 't' ) = > commands ::extend_till_char ,
key ! ( 'f' ) = > commands ::extend_next_char ,
key ! ( 'T' ) = > commands ::extend_till_prev_char ,
key ! ( 'F' ) = > commands ::extend_prev_char ,
key ! ( Home ) = > commands ::extend_line_start ,
key ! ( End ) = > commands ::extend_line_end ,
key ! ( Esc ) = > commands ::exit_select_mode ,
)
. into_iter ( ) ,
) ;
hashmap ! (
// as long as you cast the first item, rust is able to infer the other cases
// TODO: select could be normal mode with some bindings merged over
Mode ::Normal = > normal ,
Mode ::Select = > select ,
Mode ::Insert = > hashmap ! (
key ! ( Esc ) = > commands ::normal_mode as Command ,
key ! ( Backspace ) = > commands ::insert ::delete_char_backward ,
key ! ( Delete ) = > commands ::insert ::delete_char_forward ,
key ! ( Enter ) = > commands ::insert ::insert_newline ,
key ! ( Tab ) = > commands ::insert ::insert_tab ,
ctrl ! ( 'x' ) = > commands ::completion ,
ctrl ! ( 'w' ) = > commands ::insert ::delete_word_backward ,
) ,
)
impl Default for Keymaps {
fn default ( ) -> Self {
let normal = Keymap ( hashmap ! (
key ! ( 'h' ) = > Command ::move_char_left ,
key ! ( 'j' ) = > Command ::move_line_down ,
key ! ( 'k' ) = > Command ::move_line_up ,
key ! ( 'l' ) = > Command ::move_char_right ,
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 ! ( 't' ) = > Command ::find_till_char ,
key ! ( 'f' ) = > Command ::find_next_char ,
key ! ( 'T' ) = > Command ::till_prev_char ,
key ! ( 'F' ) = > Command ::find_prev_char ,
// and matching set for select mode (extend)
//
key ! ( 'r' ) = > Command ::replace ,
key ! ( 'R' ) = > Command ::replace_with_yanked ,
key ! ( Home ) = > Command ::move_line_start ,
key ! ( End ) = > Command ::move_line_end ,
key ! ( 'w' ) = > Command ::move_next_word_start ,
key ! ( 'b' ) = > Command ::move_prev_word_start ,
key ! ( 'e' ) = > Command ::move_next_word_end ,
key ! ( 'v' ) = > Command ::select_mode ,
key ! ( 'g' ) = > Command ::goto_mode ,
key ! ( ':' ) = > Command ::command_mode ,
key ! ( 'i' ) = > Command ::insert_mode ,
key ! ( 'I' ) = > Command ::prepend_to_line ,
key ! ( 'a' ) = > Command ::append_mode ,
key ! ( 'A' ) = > Command ::append_to_line ,
key ! ( 'o' ) = > Command ::open_below ,
key ! ( 'O' ) = > Command ::open_above ,
// [<space> ]<space> equivalents too (add blank new line, no edit)
key ! ( 'd' ) = > Command ::delete_selection ,
// TODO: also delete without yanking
key ! ( 'c' ) = > Command ::change_selection ,
// TODO: also change delete without yanking
// key!('r') => Command::replace_with_char,
key ! ( 's' ) = > Command ::select_regex ,
alt ! ( 's' ) = > Command ::split_selection_on_newline ,
key ! ( 'S' ) = > Command ::split_selection ,
key ! ( ';' ) = > Command ::collapse_selection ,
alt ! ( ';' ) = > Command ::flip_selections ,
key ! ( '%' ) = > Command ::select_all ,
key ! ( 'x' ) = > Command ::select_line ,
key ! ( 'X' ) = > Command ::extend_line ,
// or select mode X?
// extend_to_whole_line, crop_to_whole_line
key ! ( 'm' ) = > Command ::match_brackets ,
// TODO: refactor into
// key!('m') => commands::select_to_matching,
// key!('M') => commands::back_select_to_matching,
// select mode extend equivalents
// key!('.') => commands::repeat_insert,
// repeat_select
// TODO: figure out what key to use
// key!('[') => Command::expand_selection, ??
key ! ( '[' ) = > Command ::left_bracket_mode ,
key ! ( ']' ) = > Command ::right_bracket_mode ,
key ! ( '/' ) = > Command ::search ,
// ? for search_reverse
key ! ( 'n' ) = > Command ::search_next ,
key ! ( 'N' ) = > Command ::extend_search_next ,
// N for search_prev
key ! ( '*' ) = > Command ::search_selection ,
key ! ( 'u' ) = > Command ::undo ,
key ! ( 'U' ) = > Command ::redo ,
key ! ( 'y' ) = > Command ::yank ,
// yank_all
key ! ( 'p' ) = > Command ::paste_after ,
// paste_all
key ! ( 'P' ) = > Command ::paste_before ,
key ! ( '>' ) = > Command ::indent ,
key ! ( '<' ) = > Command ::unindent ,
key ! ( '=' ) = > Command ::format_selections ,
key ! ( 'J' ) = > Command ::join_selections ,
// TODO: conflicts hover/doc
key ! ( 'K' ) = > Command ::keep_selections ,
// TODO: and another method for inverse
// TODO: clashes with space mode
key ! ( ' ' ) = > Command ::keep_primary_selection ,
// key!('q') => Command::record_macro,
// key!('Q') => Command::replay_macro,
// ~ / apostrophe => change case
// & align selections
// _ trim selections
// C / altC = copy (repeat) selections on prev/next lines
key ! ( Esc ) = > Command ::normal_mode ,
key ! ( PageUp ) = > Command ::page_up ,
key ! ( PageDown ) = > Command ::page_down ,
ctrl ! ( 'b' ) = > Command ::page_up ,
ctrl ! ( 'f' ) = > Command ::page_down ,
ctrl ! ( 'u' ) = > Command ::half_page_up ,
ctrl ! ( 'd' ) = > Command ::half_page_down ,
ctrl ! ( 'w' ) = > Command ::window_mode ,
// move under <space>c
ctrl ! ( 'c' ) = > Command ::toggle_comments ,
key ! ( 'K' ) = > Command ::hover ,
// z family for save/restore/combine from/to sels from register
// supposedly ctrl!('i') but did not work
key ! ( Tab ) = > Command ::jump_forward ,
ctrl ! ( 'o' ) = > Command ::jump_backward ,
// ctrl!('s') => Command::save_selection,
key ! ( ' ' ) = > Command ::space_mode ,
key ! ( 'z' ) = > Command ::view_mode ,
key ! ( '"' ) = > Command ::select_register ,
) ) ;
// 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
// because some selection operations can now be done from normal mode, some from select mode.
let mut select = normal . clone ( ) ;
select . 0. extend (
hashmap ! (
key ! ( 'h' ) = > Command ::extend_char_left ,
key ! ( 'j' ) = > Command ::extend_line_down ,
key ! ( 'k' ) = > Command ::extend_line_up ,
key ! ( 'l' ) = > Command ::extend_char_right ,
key ! ( Left ) = > Command ::extend_char_left ,
key ! ( Down ) = > Command ::extend_line_down ,
key ! ( Up ) = > Command ::extend_line_up ,
key ! ( Right ) = > Command ::extend_char_right ,
key ! ( 'w' ) = > Command ::extend_next_word_start ,
key ! ( 'b' ) = > Command ::extend_prev_word_start ,
key ! ( 'e' ) = > Command ::extend_next_word_end ,
key ! ( 't' ) = > Command ::extend_till_char ,
key ! ( 'f' ) = > Command ::extend_next_char ,
key ! ( 'T' ) = > Command ::extend_till_prev_char ,
key ! ( 'F' ) = > Command ::extend_prev_char ,
key ! ( Home ) = > Command ::extend_line_start ,
key ! ( End ) = > Command ::extend_line_end ,
key ! ( Esc ) = > Command ::exit_select_mode ,
)
. into_iter ( ) ,
) ;
Keymaps ( hashmap ! (
// as long as you cast the first item, rust is able to infer the other cases
// TODO: select could be normal mode with some bindings merged over
Mode ::Normal = > normal ,
Mode ::Select = > select ,
Mode ::Insert = > Keymap ( 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 ,
ctrl ! ( 'x' ) = > Command ::completion ,
ctrl ! ( 'w' ) = > Command ::delete_word_backward ,
) ) ,
) )
}
}
// Newtype wrapper over keys to allow toml serialization/parsing
#[ derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash) ]
pub struct RepresentableKeyEvent ( pub KeyEvent ) ;
impl Display for RepresentableKeyEvent {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
let Self ( key ) = self ;
f . write_fmt ( format_args! (
"{}{}{}" ,
if key . modifiers . contains ( KeyModifiers ::SHIFT ) {
"S-"
} else {
""
} ,
if key . modifiers . contains ( KeyModifiers ::ALT ) {
"A-"
} else {
""
} ,
if key . modifiers . contains ( KeyModifiers ::CONTROL ) {
"C-"
} else {
""
} ,
) ) ? ;
match key . code {
KeyCode ::Backspace = > f . write_str ( "backspace" ) ? ,
KeyCode ::Enter = > f . write_str ( "ret" ) ? ,
KeyCode ::Left = > f . write_str ( "left" ) ? ,
KeyCode ::Right = > f . write_str ( "right" ) ? ,
KeyCode ::Up = > f . write_str ( "up" ) ? ,
KeyCode ::Down = > f . write_str ( "down" ) ? ,
KeyCode ::Home = > f . write_str ( "home" ) ? ,
KeyCode ::End = > f . write_str ( "end" ) ? ,
KeyCode ::PageUp = > f . write_str ( "pageup" ) ? ,
KeyCode ::PageDown = > f . write_str ( "pagedown" ) ? ,
KeyCode ::Tab = > f . write_str ( "tab" ) ? ,
KeyCode ::BackTab = > f . write_str ( "backtab" ) ? ,
KeyCode ::Delete = > f . write_str ( "del" ) ? ,
KeyCode ::Insert = > f . write_str ( "ins" ) ? ,
KeyCode ::Null = > f . write_str ( "null" ) ? ,
KeyCode ::Esc = > f . write_str ( "esc" ) ? ,
KeyCode ::Char ( '<' ) = > f . write_str ( "lt" ) ? ,
KeyCode ::Char ( '>' ) = > f . write_str ( "gt" ) ? ,
KeyCode ::Char ( '+' ) = > f . write_str ( "plus" ) ? ,
KeyCode ::Char ( '-' ) = > f . write_str ( "minus" ) ? ,
KeyCode ::Char ( ';' ) = > f . write_str ( "semicolon" ) ? ,
KeyCode ::Char ( '%' ) = > f . write_str ( "percent" ) ? ,
KeyCode ::F ( i ) = > f . write_fmt ( format_args! ( "F{}" , i ) ) ? ,
KeyCode ::Char ( c ) = > f . write_fmt ( format_args! ( "{}" , c ) ) ? ,
} ;
Ok ( ( ) )
}
}
impl FromStr for RepresentableKeyEvent {
type Err = Error ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
let mut tokens : Vec < _ > = s . split ( '-' ) . collect ( ) ;
let code = match tokens . pop ( ) . ok_or_else ( | | anyhow ! ( "Missing key code" ) ) ? {
"backspace" = > KeyCode ::Backspace ,
"space" = > KeyCode ::Char ( ' ' ) ,
"ret" = > KeyCode ::Enter ,
"lt" = > KeyCode ::Char ( '<' ) ,
"gt" = > KeyCode ::Char ( '>' ) ,
"plus" = > KeyCode ::Char ( '+' ) ,
"minus" = > KeyCode ::Char ( '-' ) ,
"semicolon" = > KeyCode ::Char ( ';' ) ,
"percent" = > KeyCode ::Char ( '%' ) ,
"left" = > KeyCode ::Left ,
"right" = > KeyCode ::Right ,
"up" = > KeyCode ::Down ,
"home" = > KeyCode ::Home ,
"end" = > KeyCode ::End ,
"pageup" = > KeyCode ::PageUp ,
"pagedown" = > KeyCode ::PageDown ,
"tab" = > KeyCode ::Tab ,
"backtab" = > KeyCode ::BackTab ,
"del" = > KeyCode ::Delete ,
"ins" = > KeyCode ::Insert ,
"null" = > KeyCode ::Null ,
"esc" = > KeyCode ::Esc ,
single if single . len ( ) = = 1 = > KeyCode ::Char ( single . chars ( ) . next ( ) . unwrap ( ) ) ,
function if function . len ( ) > 1 & & function . starts_with ( 'F' ) = > {
let function : String = function . chars ( ) . skip ( 1 ) . collect ( ) ;
let function = str ::parse ::< u8 > ( & function ) ? ;
( function > 0 & & function < 13 )
. then ( | | KeyCode ::F ( function ) )
. ok_or_else ( | | anyhow ! ( "Invalid function key '{}'" , function ) ) ?
}
invalid = > return Err ( anyhow ! ( "Invalid key code '{}'" , invalid ) ) ,
} ;
let mut modifiers = KeyModifiers ::empty ( ) ;
for token in tokens {
let flag = match token {
"S" = > KeyModifiers ::SHIFT ,
"A" = > KeyModifiers ::ALT ,
"C" = > KeyModifiers ::CONTROL ,
_ = > return Err ( anyhow ! ( "Invalid key modifier '{}-'" , token ) ) ,
} ;
if modifiers . contains ( flag ) {
return Err ( anyhow ! ( "Repeated key modifier '{}-'" , token ) ) ;
}
modifiers . insert ( flag ) ;
}
Ok ( RepresentableKeyEvent ( KeyEvent { code , modifiers } ) )
}
}
pub fn parse_keymaps ( toml_keymaps : & HashMap < String , HashMap < String , String > > ) -> Result < Keymaps > {
let mut keymaps = Keymaps ::default ( ) ;
for ( mode , map ) in toml_keymaps {
let mode = Mode ::from_str ( & mode ) ? ;
for ( key , command ) in map {
let key = str ::parse ::< RepresentableKeyEvent > ( & key ) ? ;
let command = str ::parse ::< Command > ( & command ) ? ;
keymaps . 0. get_mut ( & mode ) . unwrap ( ) . 0. insert ( key . 0 , command ) ;
}
}
Ok ( keymaps )
}
impl Deref for Keymap {
type Target = HashMap < KeyEvent , Command > ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
impl Deref for Keymaps {
type Target = HashMap < Mode , Keymap > ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
impl DerefMut for Keymap {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . 0
}
}
impl DerefMut for Keymaps {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . 0
}
}
#[ cfg(test) ]
mod test {
use crate ::config ::Config ;
use super ::* ;
impl PartialEq for Command {
fn eq ( & self , other : & Self ) -> bool {
self . name ( ) = = other . name ( )
}
}
#[ test ]
fn parsing_keymaps_config_file ( ) {
let sample_keymaps = r #"
[ keys . insert ]
y = "move_line_down"
S - C - a = "delete_selection"
[ keys . normal ]
A - F12 = "move_next_word_end"
" #;
let config : Config = toml ::from_str ( sample_keymaps ) . unwrap ( ) ;
assert_eq! (
* config
. keymaps
. 0
. get ( & Mode ::Insert )
. unwrap ( )
. 0
. get ( & KeyEvent {
code : KeyCode ::Char ( 'y' ) ,
modifiers : KeyModifiers ::NONE
} )
. unwrap ( ) ,
Command ::move_line_down
) ;
assert_eq! (
* config
. keymaps
. 0
. get ( & Mode ::Insert )
. unwrap ( )
. 0
. get ( & KeyEvent {
code : KeyCode ::Char ( 'a' ) ,
modifiers : KeyModifiers ::SHIFT | KeyModifiers ::CONTROL
} )
. unwrap ( ) ,
Command ::delete_selection
) ;
assert_eq! (
* config
. keymaps
. 0
. get ( & Mode ::Normal )
. unwrap ( )
. 0
. get ( & KeyEvent {
code : KeyCode ::F ( 12 ) ,
modifiers : KeyModifiers ::ALT
} )
. unwrap ( ) ,
Command ::move_next_word_end
) ;
}
#[ test ]
fn parsing_unmodified_keys ( ) {
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "backspace" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::Backspace ,
modifiers : KeyModifiers ::NONE
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "left" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::Left ,
modifiers : KeyModifiers ::NONE
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "," ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::Char ( ',' ) ,
modifiers : KeyModifiers ::NONE
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "w" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::Char ( 'w' ) ,
modifiers : KeyModifiers ::NONE
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "F12" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::F ( 12 ) ,
modifiers : KeyModifiers ::NONE
} )
) ;
}
fn parsing_modified_keys ( ) {
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "S-minus" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::Char ( '-' ) ,
modifiers : KeyModifiers ::SHIFT
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "C-A-S-F12" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::F ( 12 ) ,
modifiers : KeyModifiers ::SHIFT | KeyModifiers ::CONTROL | KeyModifiers ::ALT
} )
) ;
assert_eq! (
str ::parse ::< RepresentableKeyEvent > ( "S-C-2" ) . unwrap ( ) ,
RepresentableKeyEvent ( KeyEvent {
code : KeyCode ::F ( 2 ) ,
modifiers : KeyModifiers ::SHIFT | KeyModifiers ::CONTROL
} )
) ;
}
#[ test ]
fn parsing_nonsensical_keys_fails ( ) {
assert! ( str ::parse ::< RepresentableKeyEvent > ( "F13" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "F0" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "aaa" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "S-S-a" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "C-A-S-C-1" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "FU" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "123" ) . is_err ( ) ) ;
assert! ( str ::parse ::< RepresentableKeyEvent > ( "S--" ) . is_err ( ) ) ;
}
}