on master help sign in
Fix previous broken refactor key into helix-view
10548bfIvan Tham
helix-term/src/application.rs+3−2
@@ -44,6 +44,7 @@ pub struct Application {
44 compositor: Compositor,
44 compositor: Compositor,
45 editor: Editor,
45 editor: Editor,
46
46
47+ // TODO should be separate to take only part of the config
47 config: Config,
48 config: Config,
48
49
49 theme_loader: Arc<theme::Loader>,
50 theme_loader: Arc<theme::Loader>,
@@ -54,7 +55,7 @@ pub struct Application {
54}
55}
55
56
56impl Application {
57impl Application {
57− pub fn new(mut args: Args, config: Config) -> Result<Self, Error> {
58+ pub fn new(mut args: Args, mut config: Config) -> Result<Self, Error> {
58 use helix_view::editor::Action;
59 use helix_view::editor::Action;
59 let mut compositor = Compositor::new()?;
60 let mut compositor = Compositor::new()?;
60 let size = compositor.size();
61 let size = compositor.size();
@@ -87,7 +88,7 @@ impl Application {
87
88
88 let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone());
89 let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone());
89
90
90− let mut editor_view = Box::new(ui::EditorView::new(config.keymaps.clone()));
91+ let mut editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys)));
91 compositor.push(editor_view);
92 compositor.push(editor_view);
92
93
93 if !args.files.is_empty() {
94 if !args.files.is_empty() {
helix-term/src/commands.rs+44−27
@@ -15,6 +15,7 @@ use helix_core::{
15
15
16use helix_view::{
16use helix_view::{
17 document::{IndentStyle, Mode},
17 document::{IndentStyle, Mode},
18+ input::{KeyCode, KeyEvent},
18 view::{View, PADDING},
19 view::{View, PADDING},
19 Document, DocumentId, Editor, ViewId,
20 Document, DocumentId, Editor, ViewId,
20};
21};
@@ -42,8 +43,8 @@ use std::{
42 path::{Path, PathBuf},
43 path::{Path, PathBuf},
43};
44};
44
45
45−use crossterm::event::{KeyCode, KeyEvent};
46use once_cell::sync::Lazy;
46use once_cell::sync::Lazy;
47+use serde::de::{self, Deserialize, Deserializer};
47
48
48pub struct Context<'a> {
49pub struct Context<'a> {
49 pub selected_register: helix_view::RegisterSelection,
50 pub selected_register: helix_view::RegisterSelection,
@@ -260,6 +261,48 @@ impl Command {
260 );
261 );
261}
262}
262
263
264+impl fmt::Debug for Command {
265+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266+ let Command(name, _) = self;
267+ f.debug_tuple("Command").field(name).finish()
268+ }
269+}
270+
271+impl fmt::Display for Command {
272+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273+ let Command(name, _) = self;
274+ f.write_str(name)
275+ }
276+}
277+
278+impl std::str::FromStr for Command {
279+ type Err = anyhow::Error;
280+
281+ fn from_str(s: &str) -> Result<Self, Self::Err> {
282+ Command::COMMAND_LIST
283+ .iter()
284+ .copied()
285+ .find(|cmd| cmd.0 == s)
286+ .ok_or_else(|| anyhow!("No command named '{}'", s))
287+ }
288+}
289+
290+impl<'de> Deserialize<'de> for Command {
291+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292+ where
293+ D: Deserializer<'de>,
294+ {
295+ let s = String::deserialize(deserializer)?;
296+ s.parse().map_err(de::Error::custom)
297+ }
298+}
299+
300+impl PartialEq for Command {
301+ fn eq(&self, other: &Self) -> bool {
302+ self.name() == other.name()
303+ }
304+}
305+
263fn move_char_left(cx: &mut Context) {
306fn move_char_left(cx: &mut Context) {
264 let count = cx.count();
307 let count = cx.count();
265 let (view, doc) = current!(cx.editor);
308 let (view, doc) = current!(cx.editor);
@@ -3456,29 +3499,3 @@ fn surround_delete(cx: &mut Context) {
3456 }
3499 }
3457 })
3500 })
3458}
3501}
3459−
3460−impl fmt::Display for Command {
3461− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3462− let Command(name, _) = self;
3463− f.write_str(name)
3464− }
3465−}
3466−
3467−impl std::str::FromStr for Command {
3468− type Err = anyhow::Error;
3469−
3470− fn from_str(s: &str) -> Result<Self, Self::Err> {
3471− Command::COMMAND_LIST
3472− .iter()
3473− .copied()
3474− .find(|cmd| cmd.0 == s)
3475− .ok_or_else(|| anyhow!("No command named '{}'", s))
3476− }
3477−}
3478−
3479−impl fmt::Debug for Command {
3480− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3481− let Command(name, _) = self;
3482− f.debug_tuple("Command").field(name).finish()
3483− }
3484−}
helix-term/src/config.rs+46−31
@@ -1,47 +1,62 @@
1use anyhow::{Error, Result};
1use anyhow::{Error, Result};
2+use serde::Deserialize;
2use std::collections::HashMap;
3use std::collections::HashMap;
3
4
4−use serde::{de::Error as SerdeError, Deserialize, Serialize};
5+use crate::commands::Command;
6+use crate::keymap::Keymaps;
5
7
6−use crate::keymap::{parse_keymaps, Keymaps};
7−
8−#[derive(Default)]
8+#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
9pub struct Config {
9pub struct Config {
10 pub theme: Option<String>,
10 pub theme: Option<String>,
11+ #[serde(default)]
11 pub lsp: LspConfig,
12 pub lsp: LspConfig,
13+ #[serde(default)]
12− pub keymaps: Keymaps,
14+ pub keys: Keymaps,
13}
15}
14
16
15−#[derive(Default, Serialize, Deserialize)]
17+#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
16#[serde(rename_all = "kebab-case")]
18#[serde(rename_all = "kebab-case")]
17pub struct LspConfig {
19pub struct LspConfig {
18 pub display_messages: bool,
20 pub display_messages: bool,
19}
21}
20
22
21−#[derive(Serialize, Deserialize)]
23+#[test]
22−#[serde(rename_all = "kebab-case")]
24+fn parsing_keymaps_config_file() {
23−struct TomlConfig {
25+ use helix_core::hashmap;
24− theme: Option<String>,
26+ use helix_view::document::Mode;
25− #[serde(default)]
27+ use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
26− lsp: LspConfig,
28+
27− keys: Option<HashMap<String, HashMap<String, String>>>,
29+ let sample_keymaps = r#"
28−}
30+ [keys.insert]
31+ y = "move_line_down"
32+ S-C-a = "delete_selection"
33+
34+ [keys.normal]
35+ A-F12 = "move_next_word_end"
36+ "#;
29
37
30−impl<'de> Deserialize<'de> for Config {
38+ assert_eq!(
31− fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39+ toml::from_str::<Config>(sample_keymaps).unwrap(),
32− where
40+ Config {
33− D: serde::Deserializer<'de>,
41+ keys: Keymaps(hashmap! {
34− {
42+ Mode::Insert => hashmap! {
35− let config = TomlConfig::deserialize(deserializer)?;
43+ KeyEvent {
36− Ok(Self {
44+ code: KeyCode::Char('y'),
37− theme: config.theme,
45+ modifiers: KeyModifiers::NONE,
38− lsp: config.lsp,
46+ } => Command::move_line_down,
39− keymaps: config
47+ KeyEvent {
40− .keys
48+ code: KeyCode::Char('a'),
41− .map(|r| parse_keymaps(&r))
49+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL,
42− .transpose()
50+ } => Command::delete_selection,
43− .map_err(|e| D::Error::custom(format!("Error deserializing keymap: {}", e)))?
51+ },
44− .unwrap_or_else(Keymaps::default),
52+ Mode::Normal => hashmap! {
53+ KeyEvent {
54+ code: KeyCode::F(12),
55+ modifiers: KeyModifiers::ALT,
56+ } => Command::move_next_word_end,
57+ },
58+ }),
59+ ..Default::default()
45− })
60+ }
46− }
61+ );
47}
62}
helix-term/src/keymap.rs+77−307
@@ -1,8 +1,11 @@
1use crate::commands;
1use crate::commands;
2pub use crate::commands::Command;
2pub use crate::commands::Command;
3+use crate::config::Config;
3use anyhow::{anyhow, Error, Result};
4use anyhow::{anyhow, Error, Result};
4use helix_core::hashmap;
5use helix_core::hashmap;
5use helix_view::document::Mode;
6use helix_view::document::Mode;
7+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
8+use serde::Deserialize;
6use std::{
9use std::{
7 collections::HashMap,
10 collections::HashMap,
8 fmt::Display,
11 fmt::Display,
@@ -99,14 +102,6 @@ use std::{
99// D] = last diagnostic
102// D] = last diagnostic
100// }
103// }
101
104
102−// #[cfg(feature = "term")]
103−pub use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
104−
105−#[derive(Clone, Debug)]
106−pub struct Keymap(pub HashMap<KeyEvent, Command>);
107−#[derive(Clone, Debug)]
108−pub struct Keymaps(pub HashMap<Mode, Keymap>);
109−
110#[macro_export]
105#[macro_export]
111macro_rules! key {
106macro_rules! key {
112 ($key:ident) => {
107 ($key:ident) => {
@@ -141,9 +136,27 @@ macro_rules! alt {
141 };
136 };
142}
137}
143
138
139+#[derive(Debug, Clone, PartialEq, Deserialize)]
140+#[serde(transparent)]
141+pub struct Keymaps(pub HashMap<Mode, HashMap<KeyEvent, Command>>);
142+
143+impl Deref for Keymaps {
144+ type Target = HashMap<Mode, HashMap<KeyEvent, Command>>;
145+
146+ fn deref(&self) -> &Self::Target {
147+ &self.0
148+ }
149+}
150+
151+impl DerefMut for Keymaps {
152+ fn deref_mut(&mut self) -> &mut Self::Target {
153+ &mut self.0
154+ }
155+}
156+
144impl Default for Keymaps {
157impl Default for Keymaps {
145− fn default() -> Self {
158+ fn default() -> Keymaps {
146− let normal = Keymap(hashmap!(
159+ let normal = hashmap!(
147 key!('h') => Command::move_char_left,
160 key!('h') => Command::move_char_left,
148 key!('j') => Command::move_line_down,
161 key!('j') => Command::move_line_down,
149 key!('k') => Command::move_line_up,
162 key!('k') => Command::move_line_up,
@@ -265,12 +278,12 @@ impl Default for Keymaps {
265 key!('z') => Command::view_mode,
278 key!('z') => Command::view_mode,
266
279
267 key!('"') => Command::select_register,
280 key!('"') => Command::select_register,
268− ));
281+ );
269 // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
282 // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
270 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
283 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
271 // because some selection operations can now be done from normal mode, some from select mode.
284 // because some selection operations can now be done from normal mode, some from select mode.
272 let mut select = normal.clone();
285 let mut select = normal.clone();
273− select.0.extend(
286+ select.extend(
274 hashmap!(
287 hashmap!(
275 key!('h') => Command::extend_char_left,
288 key!('h') => Command::extend_char_left,
276 key!('j') => Command::extend_line_down,
289 key!('j') => Command::extend_line_down,
@@ -303,7 +316,7 @@ impl Default for Keymaps {
303 // TODO: select could be normal mode with some bindings merged over
316 // TODO: select could be normal mode with some bindings merged over
304 Mode::Normal => normal,
317 Mode::Normal => normal,
305 Mode::Select => select,
318 Mode::Select => select,
306− Mode::Insert => Keymap(hashmap!(
319+ Mode::Insert => hashmap!(
307 key!(Esc) => Command::normal_mode as Command,
320 key!(Esc) => Command::normal_mode as Command,
308 key!(Backspace) => Command::delete_char_backward,
321 key!(Backspace) => Command::delete_char_backward,
309 key!(Delete) => Command::delete_char_forward,
322 key!(Delete) => Command::delete_char_forward,
@@ -319,309 +332,66 @@ impl Default for Keymaps {
319 key!(End) => Command::move_line_end,
332 key!(End) => Command::move_line_end,
320 ctrl!('x') => Command::completion,
333 ctrl!('x') => Command::completion,
321 ctrl!('w') => Command::delete_word_backward,
334 ctrl!('w') => Command::delete_word_backward,
322− )),
335+ ),
323 ))
336 ))
324 }
337 }
325}
338}
326
339
327−// Newtype wrapper over keys to allow toml serialization/parsing
340+/// Merge default config keys with user overwritten keys for custom
328−#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
341+/// user config.
329−pub struct RepresentableKeyEvent(pub KeyEvent);
342+pub fn merge_keys(mut config: Config) -> Config {
330−impl Display for RepresentableKeyEvent {
343+ let mut delta = std::mem::take(&mut config.keys);
331− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344+ for (mode, keys) in &mut *config.keys {
332− let Self(key) = self;
345+ keys.extend(delta.remove(mode).unwrap_or_default());
333− f.write_fmt(format_args!(
334− "{}{}{}",
335− if key.modifiers.contains(KeyModifiers::SHIFT) {
336− "S-"
337− } else {
338− ""
339− },
340− if key.modifiers.contains(KeyModifiers::ALT) {
341− "A-"
342− } else {
343− ""
344− },
345− if key.modifiers.contains(KeyModifiers::CONTROL) {
346− "C-"
347− } else {
348− ""
349− },
350− ))?;
351− match key.code {
352− KeyCode::Backspace => f.write_str("backspace")?,
353− KeyCode::Enter => f.write_str("ret")?,
354− KeyCode::Left => f.write_str("left")?,
355− KeyCode::Right => f.write_str("right")?,
356− KeyCode::Up => f.write_str("up")?,
357− KeyCode::Down => f.write_str("down")?,
358− KeyCode::Home => f.write_str("home")?,
359− KeyCode::End => f.write_str("end")?,
360− KeyCode::PageUp => f.write_str("pageup")?,
361− KeyCode::PageDown => f.write_str("pagedown")?,
362− KeyCode::Tab => f.write_str("tab")?,
363− KeyCode::BackTab => f.write_str("backtab")?,
364− KeyCode::Delete => f.write_str("del")?,
365− KeyCode::Insert => f.write_str("ins")?,
366− KeyCode::Null => f.write_str("null")?,
367− KeyCode::Esc => f.write_str("esc")?,
368− KeyCode::Char('<') => f.write_str("lt")?,
369− KeyCode::Char('>') => f.write_str("gt")?,
370− KeyCode::Char('+') => f.write_str("plus")?,
371− KeyCode::Char('-') => f.write_str("minus")?,
372− KeyCode::Char(';') => f.write_str("semicolon")?,
373− KeyCode::Char('%') => f.write_str("percent")?,
374− KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
375− KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
376− };
377− Ok(())
378− }
379−}
380−
381−impl FromStr for RepresentableKeyEvent {
382− type Err = Error;
383−
384− fn from_str(s: &str) -> Result<Self, Self::Err> {
385− let mut tokens: Vec<_> = s.split('-').collect();
386− let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
387− "backspace" => KeyCode::Backspace,
388− "space" => KeyCode::Char(' '),
389− "ret" => KeyCode::Enter,
390− "lt" => KeyCode::Char('<'),
391− "gt" => KeyCode::Char('>'),
392− "plus" => KeyCode::Char('+'),
393− "minus" => KeyCode::Char('-'),
394− "semicolon" => KeyCode::Char(';'),
395− "percent" => KeyCode::Char('%'),
396− "left" => KeyCode::Left,
397− "right" => KeyCode::Right,
398− "up" => KeyCode::Down,
399− "home" => KeyCode::Home,
400− "end" => KeyCode::End,
401− "pageup" => KeyCode::PageUp,
402− "pagedown" => KeyCode::PageDown,
403− "tab" => KeyCode::Tab,
404− "backtab" => KeyCode::BackTab,
405− "del" => KeyCode::Delete,
406− "ins" => KeyCode::Insert,
407− "null" => KeyCode::Null,
408− "esc" => KeyCode::Esc,
409− single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
410− function if function.len() > 1 && function.starts_with('F') => {
411− let function: String = function.chars().skip(1).collect();
412− let function = str::parse::<u8>(&function)?;
413− (function > 0 && function < 13)
414− .then(|| KeyCode::F(function))
415− .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
416− }
417− invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
418− };
419−
420− let mut modifiers = KeyModifiers::empty();
421− for token in tokens {
422− let flag = match token {
423− "S" => KeyModifiers::SHIFT,
424− "A" => KeyModifiers::ALT,
425− "C" => KeyModifiers::CONTROL,
426− _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
427− };
428−
429− if modifiers.contains(flag) {
430− return Err(anyhow!("Repeated key modifier '{}-'", token));
431− }
432− modifiers.insert(flag);
433− }
434−
435− Ok(RepresentableKeyEvent(KeyEvent { code, modifiers }))
436− }
437−}
438−
439−pub fn parse_keymaps(toml_keymaps: &HashMap<String, HashMap<String, String>>) -> Result<Keymaps> {
440− let mut keymaps = Keymaps::default();
441−
442− for (mode, map) in toml_keymaps {
443− let mode = Mode::from_str(&mode)?;
444− for (key, command) in map {
445− let key = str::parse::<RepresentableKeyEvent>(&key)?;
446− let command = str::parse::<Command>(&command)?;
447− keymaps.0.get_mut(&mode).unwrap().0.insert(key.0, command);
448− }
449− }
450− Ok(keymaps)
451−}
452−
453−impl Deref for Keymap {
454− type Target = HashMap<KeyEvent, Command>;
455− fn deref(&self) -> &Self::Target {
456− &self.0
457− }
458−}
459−
460−impl Deref for Keymaps {
461− type Target = HashMap<Mode, Keymap>;
462− fn deref(&self) -> &Self::Target {
463− &self.0
464− }
465−}
466−
467−impl DerefMut for Keymap {
468− fn deref_mut(&mut self) -> &mut Self::Target {
469− &mut self.0
470− }
471−}
472−
473−impl DerefMut for Keymaps {
474− fn deref_mut(&mut self) -> &mut Self::Target {
475− &mut self.0
476 }
346 }
347+ config
477}
348}
478
349
479−#[cfg(test)]
480−mod test {
481− use crate::config::Config;
482−
483− use super::*;
484−
485− impl PartialEq for Command {
486− fn eq(&self, other: &Self) -> bool {
487− self.name() == other.name()
488− }
489− }
490−
491− #[test]
492− fn parsing_keymaps_config_file() {
493− let sample_keymaps = r#"
494− [keys.insert]
495− y = "move_line_down"
496− S-C-a = "delete_selection"
497−
498− [keys.normal]
499− A-F12 = "move_next_word_end"
500− "#;
501−
502− let config: Config = toml::from_str(sample_keymaps).unwrap();
503− assert_eq!(
504− *config
505− .keymaps
506− .0
507− .get(&Mode::Insert)
508− .unwrap()
509− .0
510− .get(&KeyEvent {
511− code: KeyCode::Char('y'),
512− modifiers: KeyModifiers::NONE
513− })
514− .unwrap(),
515− Command::move_line_down
516− );
517− assert_eq!(
518− *config
519− .keymaps
520− .0
521− .get(&Mode::Insert)
522− .unwrap()
523− .0
524− .get(&KeyEvent {
525− code: KeyCode::Char('a'),
526− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
527− })
528− .unwrap(),
529− Command::delete_selection
530− );
531− assert_eq!(
532− *config
533− .keymaps
534− .0
535− .get(&Mode::Normal)
536− .unwrap()
537− .0
538− .get(&KeyEvent {
539− code: KeyCode::F(12),
540− modifiers: KeyModifiers::ALT
541− })
542− .unwrap(),
543− Command::move_next_word_end
544− );
545− }
546−
547− #[test]
350+#[test]
548− fn parsing_unmodified_keys() {
351+fn merge_partial_keys() {
549− assert_eq!(
352+ let config = Config {
550− str::parse::<RepresentableKeyEvent>("backspace").unwrap(),
353+ keys: Keymaps(hashmap! {
354+ Mode::Normal => hashmap! {
551− RepresentableKeyEvent(KeyEvent {
355+ KeyEvent {
552− code: KeyCode::Backspace,
356+ code: KeyCode::Char('i'),
553− modifiers: KeyModifiers::NONE
357+ modifiers: KeyModifiers::NONE,
554− })
358+ } => Command::normal_mode,
555− );
359+ KeyEvent { // key that does not exist
556−
557− assert_eq!(
558− str::parse::<RepresentableKeyEvent>("left").unwrap(),
559− RepresentableKeyEvent(KeyEvent {
560− code: KeyCode::Left,
360+ code: KeyCode::Char('无'),
561− modifiers: KeyModifiers::NONE
361+ modifiers: KeyModifiers::NONE,
362+ } => Command::insert_mode,
562− })
363+ },
563− );
364+ }),
564−
365+ ..Default::default()
565− assert_eq!(
366+ };
566− str::parse::<RepresentableKeyEvent>(",").unwrap(),
367+ let merged_config = merge_keys(config.clone());
567− RepresentableKeyEvent(KeyEvent {
368+ assert_ne!(config, merged_config);
568− code: KeyCode::Char(','),
569− modifiers: KeyModifiers::NONE
570− })
571− );
572−
573− assert_eq!(
369+ assert_eq!(
574− str::parse::<RepresentableKeyEvent>("w").unwrap(),
370+ *merged_config
371+ .keys
372+ .0
373+ .get(&Mode::Normal)
374+ .unwrap()
575− RepresentableKeyEvent(KeyEvent {
375+ .get(&KeyEvent {
576− code: KeyCode::Char('w'),
376+ code: KeyCode::Char('i'),
577 modifiers: KeyModifiers::NONE
377 modifiers: KeyModifiers::NONE
578 })
378 })
379+ .unwrap(),
380+ Command::normal_mode
579− );
381+ );
580−
581− assert_eq!(
382+ assert_eq!(
582− str::parse::<RepresentableKeyEvent>("F12").unwrap(),
383+ *merged_config
384+ .keys
385+ .0
386+ .get(&Mode::Normal)
387+ .unwrap()
583− RepresentableKeyEvent(KeyEvent {
388+ .get(&KeyEvent {
584− code: KeyCode::F(12),
389+ code: KeyCode::Char('无'),
585 modifiers: KeyModifiers::NONE
390 modifiers: KeyModifiers::NONE
586 })
391 })
587− );
392+ .unwrap(),
588− }
393+ Command::insert_mode
589−
590− fn parsing_modified_keys() {
591− assert_eq!(
592− str::parse::<RepresentableKeyEvent>("S-minus").unwrap(),
593− RepresentableKeyEvent(KeyEvent {
594− code: KeyCode::Char('-'),
595− modifiers: KeyModifiers::SHIFT
596− })
597− );
598−
599− assert_eq!(
600− str::parse::<RepresentableKeyEvent>("C-A-S-F12").unwrap(),
601− RepresentableKeyEvent(KeyEvent {
602− code: KeyCode::F(12),
603− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
604− })
605− );
606−
607− assert_eq!(
608− str::parse::<RepresentableKeyEvent>("S-C-2").unwrap(),
609− RepresentableKeyEvent(KeyEvent {
610− code: KeyCode::F(2),
611− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
612− })
613− );
394+ );
614− }
395+ assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
615−
396+ assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
616− #[test]
617− fn parsing_nonsensical_keys_fails() {
618− assert!(str::parse::<RepresentableKeyEvent>("F13").is_err());
619− assert!(str::parse::<RepresentableKeyEvent>("F0").is_err());
620− assert!(str::parse::<RepresentableKeyEvent>("aaa").is_err());
621− assert!(str::parse::<RepresentableKeyEvent>("S-S-a").is_err());
622− assert!(str::parse::<RepresentableKeyEvent>("C-A-S-C-1").is_err());
623− assert!(str::parse::<RepresentableKeyEvent>("FU").is_err());
624− assert!(str::parse::<RepresentableKeyEvent>("123").is_err());
625− assert!(str::parse::<RepresentableKeyEvent>("S--").is_err());
626− }
627}
397}
helix-term/src/main.rs+7−8
@@ -1,10 +1,10 @@
1+use anyhow::{Context, Error, Result};
1use helix_term::application::Application;
2use helix_term::application::Application;
2use helix_term::args::Args;
3use helix_term::args::Args;
3use helix_term::config::Config;
4use helix_term::config::Config;
5+use helix_term::keymap::merge_keys;
4use std::path::PathBuf;
6use std::path::PathBuf;
5
7
6−use anyhow::{Context, Result};
7−
8fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
8fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
9 let mut base_config = fern::Dispatch::new();
9 let mut base_config = fern::Dispatch::new();
10
10
@@ -84,12 +84,11 @@ FLAGS:
84 std::fs::create_dir_all(&conf_dir).ok();
84 std::fs::create_dir_all(&conf_dir).ok();
85 }
85 }
86
86
87− let config = std::fs::read_to_string(conf_dir.join("config.toml"))
87+ let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
88− .ok()
89− .map(|s| toml::from_str(&s))
88+ Ok(config) => merge_keys(toml::from_str(&config)?),
90− .transpose()?
89+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
91− .or_else(|| Some(Config::default()))
90+ Err(err) => return Err(Error::new(err)),
92− .unwrap();
91+ };
93
92
94 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
93 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
95
94
helix-term/src/ui/editor.rs+4−2
@@ -12,12 +12,13 @@ use helix_core::{
12 LineEnding, Position, Range,
12 LineEnding, Position, Range,
13};
13};
14use helix_lsp::LspProgressMap;
14use helix_lsp::LspProgressMap;
15+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
15use helix_view::{document::Mode, Document, Editor, Theme, View};
16use helix_view::{document::Mode, Document, Editor, Theme, View};
16use std::borrow::Cow;
17use std::borrow::Cow;
17
18
18use crossterm::{
19use crossterm::{
19 cursor,
20 cursor,
20− event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
21+ event::{read, Event, EventStream},
21};
22};
22use tui::{
23use tui::{
23 backend::CrosstermBackend,
24 backend::CrosstermBackend,
@@ -659,7 +660,8 @@ impl Component for EditorView {
659 cx.editor.resize(Rect::new(0, 0, width, height - 1));
660 cx.editor.resize(Rect::new(0, 0, width, height - 1));
660 EventResult::Consumed(None)
661 EventResult::Consumed(None)
661 }
662 }
662− Event::Key(mut key) => {
663+ Event::Key(key) => {
664+ let mut key = KeyEvent::from(key);
663 canonicalize_key(&mut key);
665 canonicalize_key(&mut key);
664 // clear status
666 // clear status
665 cx.editor.status_msg = None;
667 cx.editor.status_msg = None;
helix-view/src/document.rs+36−25
@@ -1,5 +1,7 @@
1use anyhow::{anyhow, Context, Error};
1use anyhow::{anyhow, Context, Error};
2+use serde::de::{self, Deserialize, Deserializer};
2use std::cell::Cell;
3use std::cell::Cell;
4+use std::collections::HashMap;
3use std::fmt::Display;
5use std::fmt::Display;
4use std::future::Future;
6use std::future::Future;
5use std::path::{Component, Path, PathBuf};
7use std::path::{Component, Path, PathBuf};
@@ -17,8 +19,6 @@ use helix_core::{
17
19
18use crate::{DocumentId, Theme, ViewId};
20use crate::{DocumentId, Theme, ViewId};
19
21
20−use std::collections::HashMap;
21−
22const BUF_SIZE: usize = 8192;
22const BUF_SIZE: usize = 8192;
23
23
24#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
24#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -28,6 +28,40 @@ pub enum Mode {
28 Insert,
28 Insert,
29}
29}
30
30
31+impl Display for Mode {
32+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33+ match self {
34+ Mode::Normal => f.write_str("normal"),
35+ Mode::Select => f.write_str("select"),
36+ Mode::Insert => f.write_str("insert"),
37+ }
38+ }
39+}
40+
41+impl FromStr for Mode {
42+ type Err = Error;
43+
44+ fn from_str(s: &str) -> Result<Self, Self::Err> {
45+ match s {
46+ "normal" => Ok(Mode::Normal),
47+ "select" => Ok(Mode::Select),
48+ "insert" => Ok(Mode::Insert),
49+ _ => Err(anyhow!("Invalid mode '{}'", s)),
50+ }
51+ }
52+}
53+
54+// toml deserializer doesn't seem to recognize string as enum
55+impl<'de> Deserialize<'de> for Mode {
56+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57+ where
58+ D: Deserializer<'de>,
59+ {
60+ let s = String::deserialize(deserializer)?;
61+ s.parse().map_err(de::Error::custom)
62+ }
63+}
64+
31#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
65#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
32pub enum IndentStyle {
66pub enum IndentStyle {
33 Tabs,
67 Tabs,
@@ -97,29 +131,6 @@ impl fmt::Debug for Document {
97 }
131 }
98}
132}
99
133
100−impl Display for Mode {
101− fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102− match self {
103− Mode::Normal => f.write_str("normal"),
104− Mode::Select => f.write_str("select"),
105− Mode::Insert => f.write_str("insert"),
106− }
107− }
108−}
109−
110−impl FromStr for Mode {
111− type Err = Error;
112−
113− fn from_str(s: &str) -> Result<Self, Self::Err> {
114− match s {
115− "normal" => Ok(Mode::Normal),
116− "select" => Ok(Mode::Select),
117− "insert" => Ok(Mode::Insert),
118− _ => Err(anyhow!("Invalid mode '{}'", s)),
119− }
120− }
121−}
122−
123// The documentation and implementation of this function should be up-to-date with
134// The documentation and implementation of this function should be up-to-date with
124// its sibling function, `to_writer()`.
135// its sibling function, `to_writer()`.
125//
136//
helix-view/src/input.rsnew file+226
@@ -0,0 +1,226 @@
1+//! Input event handling, currently backed by crossterm.
2+use anyhow::{anyhow, Error};
3+use crossterm::event;
4+use serde::de::{self, Deserialize, Deserializer};
5+use std::fmt;
6+
7+pub use crossterm::event::{KeyCode, KeyModifiers};
8+
9+/// Represents a key event.
10+// We use a newtype here because we want to customize Deserialize and Display.
11+#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
12+pub struct KeyEvent {
13+ pub code: KeyCode,
14+ pub modifiers: KeyModifiers,
15+}
16+
17+impl fmt::Display for KeyEvent {
18+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
19+ f.write_fmt(format_args!(
20+ "{}{}{}",
21+ if self.modifiers.contains(KeyModifiers::SHIFT) {
22+ "S-"
23+ } else {
24+ ""
25+ },
26+ if self.modifiers.contains(KeyModifiers::ALT) {
27+ "A-"
28+ } else {
29+ ""
30+ },
31+ if self.modifiers.contains(KeyModifiers::CONTROL) {
32+ "C-"
33+ } else {
34+ ""
35+ },
36+ ))?;
37+ match self.code {
38+ KeyCode::Backspace => f.write_str("backspace")?,
39+ KeyCode::Enter => f.write_str("ret")?,
40+ KeyCode::Left => f.write_str("left")?,
41+ KeyCode::Right => f.write_str("right")?,
42+ KeyCode::Up => f.write_str("up")?,
43+ KeyCode::Down => f.write_str("down")?,
44+ KeyCode::Home => f.write_str("home")?,
45+ KeyCode::End => f.write_str("end")?,
46+ KeyCode::PageUp => f.write_str("pageup")?,
47+ KeyCode::PageDown => f.write_str("pagedown")?,
48+ KeyCode::Tab => f.write_str("tab")?,
49+ KeyCode::BackTab => f.write_str("backtab")?,
50+ KeyCode::Delete => f.write_str("del")?,
51+ KeyCode::Insert => f.write_str("ins")?,
52+ KeyCode::Null => f.write_str("null")?,
53+ KeyCode::Esc => f.write_str("esc")?,
54+ KeyCode::Char('<') => f.write_str("lt")?,
55+ KeyCode::Char('>') => f.write_str("gt")?,
56+ KeyCode::Char('+') => f.write_str("plus")?,
57+ KeyCode::Char('-') => f.write_str("minus")?,
58+ KeyCode::Char(';') => f.write_str("semicolon")?,
59+ KeyCode::Char('%') => f.write_str("percent")?,
60+ KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
61+ KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
62+ };
63+ Ok(())
64+ }
65+}
66+
67+impl std::str::FromStr for KeyEvent {
68+ type Err = Error;
69+
70+ fn from_str(s: &str) -> Result<Self, Self::Err> {
71+ let mut tokens: Vec<_> = s.split('-').collect();
72+ let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
73+ "backspace" => KeyCode::Backspace,
74+ "space" => KeyCode::Char(' '),
75+ "ret" => KeyCode::Enter,
76+ "lt" => KeyCode::Char('<'),
77+ "gt" => KeyCode::Char('>'),
78+ "plus" => KeyCode::Char('+'),
79+ "minus" => KeyCode::Char('-'),
80+ "semicolon" => KeyCode::Char(';'),
81+ "percent" => KeyCode::Char('%'),
82+ "left" => KeyCode::Left,
83+ "right" => KeyCode::Right,
84+ "up" => KeyCode::Down,
85+ "home" => KeyCode::Home,
86+ "end" => KeyCode::End,
87+ "pageup" => KeyCode::PageUp,
88+ "pagedown" => KeyCode::PageDown,
89+ "tab" => KeyCode::Tab,
90+ "backtab" => KeyCode::BackTab,
91+ "del" => KeyCode::Delete,
92+ "ins" => KeyCode::Insert,
93+ "null" => KeyCode::Null,
94+ "esc" => KeyCode::Esc,
95+ single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
96+ function if function.len() > 1 && function.starts_with('F') => {
97+ let function: String = function.chars().skip(1).collect();
98+ let function = str::parse::<u8>(&function)?;
99+ (function > 0 && function < 13)
100+ .then(|| KeyCode::F(function))
101+ .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
102+ }
103+ invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
104+ };
105+
106+ let mut modifiers = KeyModifiers::empty();
107+ for token in tokens {
108+ let flag = match token {
109+ "S" => KeyModifiers::SHIFT,
110+ "A" => KeyModifiers::ALT,
111+ "C" => KeyModifiers::CONTROL,
112+ _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
113+ };
114+
115+ if modifiers.contains(flag) {
116+ return Err(anyhow!("Repeated key modifier '{}-'", token));
117+ }
118+ modifiers.insert(flag);
119+ }
120+
121+ Ok(KeyEvent { code, modifiers })
122+ }
123+}
124+
125+impl<'de> Deserialize<'de> for KeyEvent {
126+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127+ where
128+ D: Deserializer<'de>,
129+ {
130+ let s = String::deserialize(deserializer)?;
131+ s.parse().map_err(de::Error::custom)
132+ }
133+}
134+
135+impl From<event::KeyEvent> for KeyEvent {
136+ fn from(event::KeyEvent { code, modifiers }: event::KeyEvent) -> KeyEvent {
137+ KeyEvent { code, modifiers }
138+ }
139+}
140+
141+#[cfg(test)]
142+mod test {
143+ use super::*;
144+
145+ #[test]
146+ fn parsing_unmodified_keys() {
147+ assert_eq!(
148+ str::parse::<KeyEvent>("backspace").unwrap(),
149+ KeyEvent {
150+ code: KeyCode::Backspace,
151+ modifiers: KeyModifiers::NONE
152+ }
153+ );
154+
155+ assert_eq!(
156+ str::parse::<KeyEvent>("left").unwrap(),
157+ KeyEvent {
158+ code: KeyCode::Left,
159+ modifiers: KeyModifiers::NONE
160+ }
161+ );
162+
163+ assert_eq!(
164+ str::parse::<KeyEvent>(",").unwrap(),
165+ KeyEvent {
166+ code: KeyCode::Char(','),
167+ modifiers: KeyModifiers::NONE
168+ }
169+ );
170+
171+ assert_eq!(
172+ str::parse::<KeyEvent>("w").unwrap(),
173+ KeyEvent {
174+ code: KeyCode::Char('w'),
175+ modifiers: KeyModifiers::NONE
176+ }
177+ );
178+
179+ assert_eq!(
180+ str::parse::<KeyEvent>("F12").unwrap(),
181+ KeyEvent {
182+ code: KeyCode::F(12),
183+ modifiers: KeyModifiers::NONE
184+ }
185+ );
186+ }
187+
188+ #[test]
189+ fn parsing_modified_keys() {
190+ assert_eq!(
191+ str::parse::<KeyEvent>("S-minus").unwrap(),
192+ KeyEvent {
193+ code: KeyCode::Char('-'),
194+ modifiers: KeyModifiers::SHIFT
195+ }
196+ );
197+
198+ assert_eq!(
199+ str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
200+ KeyEvent {
201+ code: KeyCode::F(12),
202+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
203+ }
204+ );
205+
206+ assert_eq!(
207+ str::parse::<KeyEvent>("S-C-2").unwrap(),
208+ KeyEvent {
209+ code: KeyCode::Char('2'),
210+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
211+ }
212+ );
213+ }
214+
215+ #[test]
216+ fn parsing_nonsensical_keys_fails() {
217+ assert!(str::parse::<KeyEvent>("F13").is_err());
218+ assert!(str::parse::<KeyEvent>("F0").is_err());
219+ assert!(str::parse::<KeyEvent>("aaa").is_err());
220+ assert!(str::parse::<KeyEvent>("S-S-a").is_err());
221+ assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
222+ assert!(str::parse::<KeyEvent>("FU").is_err());
223+ assert!(str::parse::<KeyEvent>("123").is_err());
224+ assert!(str::parse::<KeyEvent>("S--").is_err());
225+ }
226+}
helix-view/src/lib.rs+5−3
@@ -4,14 +4,16 @@ pub mod macros;
4pub mod clipboard;
4pub mod clipboard;
5pub mod document;
5pub mod document;
6pub mod editor;
6pub mod editor;
7+pub mod input;
7pub mod register_selection;
8pub mod register_selection;
8pub mod theme;
9pub mod theme;
9pub mod tree;
10pub mod tree;
10pub mod view;
11pub mod view;
11
12
12−use slotmap::new_key_type;
13+slotmap::new_key_type! {
13−new_key_type! { pub struct DocumentId; }
14+ pub struct DocumentId;
14−new_key_type! { pub struct ViewId; }
15+ pub struct ViewId;
16+}
15
17
16pub use document::Document;
18pub use document::Document;
17pub use editor::Editor;
19pub use editor::Editor;