on master help sign in
Refactor key into helix-view

Now also make use of Deserialize for Config.

commitca806d4f852e934651132fc9570a6110e30f646d authorIvan Tham <pickfire@riseup.net> dateFri 18 Jun 2021, 00:52 (5 years ago) parent1c25852
9 files changed+388−414
helix-term/src/application.rs+2−2 helix-term/src/commands.rs+44−27 helix-term/src/config.rs+45−31 helix-term/src/keymap.rs+20−316 helix-term/src/main.rs+6−8 helix-term/src/ui/editor.rs+4−2 helix-view/src/document.rs+36−25 helix-view/src/input.rs+226 helix-view/src/lib.rs+5−3
helix-term/src/application.rs+2−2
@@ -1,7 +1,7 @@
11use helix_lsp::{lsp, LspProgressMap};
22use helix_view::{document::Mode, Document, Editor, Theme, View};
33
4−use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
4+use crate::{args::Args, compositor::Compositor, config::Config, ui};
55
66use log::{error, info};
77
@@ -49,7 +49,7 @@ impl Application {
4949 let size = compositor.size();
5050 let mut editor = Editor::new(size);
5151
52− let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
52+ let mut editor_view = Box::new(ui::EditorView::new(config.keys));
5353 compositor.push(editor_view);
5454
5555 if !args.files.is_empty() {
helix-term/src/commands.rs+44−27
@@ -11,6 +11,7 @@ use helix_core::{
1111
1212use helix_view::{
1313 document::{IndentStyle, Mode},
14+ input::{KeyCode, KeyEvent},
1415 view::{View, PADDING},
1516 Document, DocumentId, Editor, ViewId,
1617};
@@ -38,8 +39,8 @@ use std::{
3839 path::{Path, PathBuf},
3940};
4041
41−use crossterm::event::{KeyCode, KeyEvent};
4242use once_cell::sync::Lazy;
43+use serde::de::{self, Deserialize, Deserializer};
4344
4445pub struct Context<'a> {
4546 pub selected_register: helix_view::RegisterSelection,
@@ -252,6 +253,48 @@ impl Command {
252253 );
253254}
254255
256+impl fmt::Debug for Command {
257+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258+ let Command(name, _) = self;
259+ f.debug_tuple("Command").field(name).finish()
260+ }
261+}
262+
263+impl fmt::Display for Command {
264+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265+ let Command(name, _) = self;
266+ f.write_str(name)
267+ }
268+}
269+
270+impl std::str::FromStr for Command {
271+ type Err = anyhow::Error;
272+
273+ fn from_str(s: &str) -> Result<Self, Self::Err> {
274+ Command::COMMAND_LIST
275+ .iter()
276+ .copied()
277+ .find(|cmd| cmd.0 == s)
278+ .ok_or_else(|| anyhow!("No command named '{}'", s))
279+ }
280+}
281+
282+impl<'de> Deserialize<'de> for Command {
283+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284+ where
285+ D: Deserializer<'de>,
286+ {
287+ let s = String::deserialize(deserializer)?;
288+ s.parse().map_err(de::Error::custom)
289+ }
290+}
291+
292+impl PartialEq for Command {
293+ fn eq(&self, other: &Self) -> bool {
294+ self.name() == other.name()
295+ }
296+}
297+
255298fn move_char_left(cx: &mut Context) {
256299 let count = cx.count();
257300 let (view, doc) = current!(cx.editor);
@@ -3042,29 +3085,3 @@ fn right_bracket_mode(cx: &mut Context) {
30423085 }
30433086 })
30443087}
3045−
3046−impl fmt::Display for Command {
3047− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3048− let Command(name, _) = self;
3049− f.write_str(name)
3050− }
3051−}
3052−
3053−impl std::str::FromStr for Command {
3054− type Err = anyhow::Error;
3055−
3056− fn from_str(s: &str) -> Result<Self, Self::Err> {
3057− Command::COMMAND_LIST
3058− .iter()
3059− .copied()
3060− .find(|cmd| cmd.0 == s)
3061− .ok_or_else(|| anyhow!("No command named '{}'", s))
3062− }
3063−}
3064−
3065−impl fmt::Debug for Command {
3066− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3067− let Command(name, _) = self;
3068− f.debug_tuple("Command").field(name).finish()
3069− }
3070−}
helix-term/src/config.rs+45−31
@@ -1,10 +1,9 @@
1−use anyhow::{Error, Result};
2−use std::{collections::HashMap, str::FromStr};
1+use serde::Deserialize;
32
4−use serde::{de::Error as SerdeError, Deserialize, Serialize};
5−
6−use crate::keymap::{parse_keymaps, Keymaps};
3+use crate::commands::Command;
4+use crate::keymap::Keymaps;
75
6+#[derive(Debug, PartialEq, Deserialize)]
87pub struct GlobalConfig {
98 pub lsp_progress: bool,
109}
@@ -15,35 +14,50 @@ impl Default for GlobalConfig {
1514 }
1615}
1716
18−#[derive(Default)]
17+#[derive(Debug, Default, PartialEq, Deserialize)]
18+#[serde(default)]
1919pub struct Config {
2020 pub global: GlobalConfig,
21− pub keymaps: Keymaps,
21+ pub keys: Keymaps,
2222}
2323
24−#[derive(Serialize, Deserialize)]
25−#[serde(rename_all = "kebab-case")]
26−struct TomlConfig {
27− lsp_progress: Option<bool>,
28− keys: Option<HashMap<String, HashMap<String, String>>>,
29−}
24+#[test]
25+fn parsing_keymaps_config_file() {
26+ use helix_core::hashmap;
27+ use helix_view::document::Mode;
28+ use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
3029
31−impl<'de> Deserialize<'de> for Config {
32− fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33− where
34− D: serde::Deserializer<'de>,
35− {
36− let config = TomlConfig::deserialize(deserializer)?;
37− Ok(Self {
38− global: GlobalConfig {
39− lsp_progress: config.lsp_progress.unwrap_or(true),
40− },
41− keymaps: config
42− .keys
43− .map(|r| parse_keymaps(&r))
44− .transpose()
45− .map_err(|e| D::Error::custom(format!("Error deserializing keymap: {}", e)))?
46− .unwrap_or_else(Keymaps::default),
47− })
48− }
30+ let sample_keymaps = r#"
31+ [keys.insert]
32+ y = "move_line_down"
33+ S-C-a = "delete_selection"
34+
35+ [keys.normal]
36+ A-F12 = "move_next_word_end"
37+ "#;
38+
39+ assert_eq!(
40+ toml::from_str::<Config>(sample_keymaps).unwrap(),
41+ Config {
42+ global: Default::default(),
43+ keys: Keymaps(hashmap! {
44+ Mode::Insert => hashmap! {
45+ KeyEvent {
46+ code: KeyCode::Char('y'),
47+ modifiers: KeyModifiers::NONE,
48+ } => Command::move_line_down,
49+ KeyEvent {
50+ code: KeyCode::Char('a'),
51+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL,
52+ } => Command::delete_selection,
53+ },
54+ Mode::Normal => hashmap! {
55+ KeyEvent {
56+ code: KeyCode::F(12),
57+ modifiers: KeyModifiers::ALT,
58+ } => Command::move_next_word_end,
59+ },
60+ })
61+ }
62+ );
4963}
helix-term/src/keymap.rs+20−316
@@ -3,6 +3,8 @@ pub use crate::commands::Command;
33use anyhow::{anyhow, Error, Result};
44use helix_core::hashmap;
55use helix_view::document::Mode;
6+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
7+use serde::Deserialize;
68use std::{
79 collections::HashMap,
810 fmt::Display,
@@ -99,14 +101,6 @@ use std::{
99101// D] = last diagnostic
100102// }
101103
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−
110104#[macro_export]
111105macro_rules! key {
112106 ($key:ident) => {
@@ -141,9 +135,21 @@ macro_rules! alt {
141135 };
142136}
143137
138+#[derive(Debug, PartialEq, Deserialize)]
139+#[serde(transparent)]
140+pub struct Keymaps(pub HashMap<Mode, HashMap<KeyEvent, Command>>);
141+
142+impl Deref for Keymaps {
143+ type Target = HashMap<Mode, HashMap<KeyEvent, Command>>;
144+
145+ fn deref(&self) -> &Self::Target {
146+ &self.0
147+ }
148+}
149+
144150impl Default for Keymaps {
145− fn default() -> Self {
146− let normal = Keymap(hashmap!(
151+ fn default() -> Keymaps {
152+ let normal = hashmap!(
147153 key!('h') => Command::move_char_left,
148154 key!('j') => Command::move_line_down,
149155 key!('k') => Command::move_line_up,
@@ -277,12 +283,12 @@ impl Default for Keymaps {
277283 key!('z') => Command::view_mode,
278284
279285 key!('"') => Command::select_register,
280− ));
286+ );
281287 // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
282288 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
283289 // because some selection operations can now be done from normal mode, some from select mode.
284290 let mut select = normal.clone();
285− select.0.extend(
291+ select.extend(
286292 hashmap!(
287293 key!('h') => Command::extend_char_left,
288294 key!('j') => Command::extend_line_down,
@@ -315,7 +321,7 @@ impl Default for Keymaps {
315321 // TODO: select could be normal mode with some bindings merged over
316322 Mode::Normal => normal,
317323 Mode::Select => select,
318− Mode::Insert => Keymap(hashmap!(
324+ Mode::Insert => hashmap!(
319325 key!(Esc) => Command::normal_mode as Command,
320326 key!(Backspace) => Command::delete_char_backward,
321327 key!(Delete) => Command::delete_char_forward,
@@ -329,309 +335,7 @@ impl Default for Keymaps {
329335 key!(PageDown) => Command::page_down,
330336 ctrl!('x') => Command::completion,
331337 ctrl!('w') => Command::delete_word_backward,
332− )),
338+ ),
333339 ))
334340 }
335341}
336−
337−// Newtype wrapper over keys to allow toml serialization/parsing
338−#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
339−pub struct RepresentableKeyEvent(pub KeyEvent);
340−impl Display for RepresentableKeyEvent {
341− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342− let Self(key) = self;
343− f.write_fmt(format_args!(
344− "{}{}{}",
345− if key.modifiers.contains(KeyModifiers::SHIFT) {
346− "S-"
347− } else {
348− ""
349− },
350− if key.modifiers.contains(KeyModifiers::ALT) {
351− "A-"
352− } else {
353− ""
354− },
355− if key.modifiers.contains(KeyModifiers::CONTROL) {
356− "C-"
357− } else {
358− ""
359− },
360− ))?;
361− match key.code {
362− KeyCode::Backspace => f.write_str("backspace")?,
363− KeyCode::Enter => f.write_str("ret")?,
364− KeyCode::Left => f.write_str("left")?,
365− KeyCode::Right => f.write_str("right")?,
366− KeyCode::Up => f.write_str("up")?,
367− KeyCode::Down => f.write_str("down")?,
368− KeyCode::Home => f.write_str("home")?,
369− KeyCode::End => f.write_str("end")?,
370− KeyCode::PageUp => f.write_str("pageup")?,
371− KeyCode::PageDown => f.write_str("pagedown")?,
372− KeyCode::Tab => f.write_str("tab")?,
373− KeyCode::BackTab => f.write_str("backtab")?,
374− KeyCode::Delete => f.write_str("del")?,
375− KeyCode::Insert => f.write_str("ins")?,
376− KeyCode::Null => f.write_str("null")?,
377− KeyCode::Esc => f.write_str("esc")?,
378− KeyCode::Char('<') => f.write_str("lt")?,
379− KeyCode::Char('>') => f.write_str("gt")?,
380− KeyCode::Char('+') => f.write_str("plus")?,
381− KeyCode::Char('-') => f.write_str("minus")?,
382− KeyCode::Char(';') => f.write_str("semicolon")?,
383− KeyCode::Char('%') => f.write_str("percent")?,
384− KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
385− KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
386− };
387− Ok(())
388− }
389−}
390−
391−impl FromStr for RepresentableKeyEvent {
392− type Err = Error;
393−
394− fn from_str(s: &str) -> Result<Self, Self::Err> {
395− let mut tokens: Vec<_> = s.split('-').collect();
396− let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
397− "backspace" => KeyCode::Backspace,
398− "space" => KeyCode::Char(' '),
399− "ret" => KeyCode::Enter,
400− "lt" => KeyCode::Char('<'),
401− "gt" => KeyCode::Char('>'),
402− "plus" => KeyCode::Char('+'),
403− "minus" => KeyCode::Char('-'),
404− "semicolon" => KeyCode::Char(';'),
405− "percent" => KeyCode::Char('%'),
406− "left" => KeyCode::Left,
407− "right" => KeyCode::Right,
408− "up" => KeyCode::Down,
409− "home" => KeyCode::Home,
410− "end" => KeyCode::End,
411− "pageup" => KeyCode::PageUp,
412− "pagedown" => KeyCode::PageDown,
413− "tab" => KeyCode::Tab,
414− "backtab" => KeyCode::BackTab,
415− "del" => KeyCode::Delete,
416− "ins" => KeyCode::Insert,
417− "null" => KeyCode::Null,
418− "esc" => KeyCode::Esc,
419− single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
420− function if function.len() > 1 && function.starts_with('F') => {
421− let function: String = function.chars().skip(1).collect();
422− let function = str::parse::<u8>(&function)?;
423− (function > 0 && function < 13)
424− .then(|| KeyCode::F(function))
425− .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
426− }
427− invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
428− };
429−
430− let mut modifiers = KeyModifiers::empty();
431− for token in tokens {
432− let flag = match token {
433− "S" => KeyModifiers::SHIFT,
434− "A" => KeyModifiers::ALT,
435− "C" => KeyModifiers::CONTROL,
436− _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
437− };
438−
439− if modifiers.contains(flag) {
440− return Err(anyhow!("Repeated key modifier '{}-'", token));
441− }
442− modifiers.insert(flag);
443− }
444−
445− Ok(RepresentableKeyEvent(KeyEvent { code, modifiers }))
446− }
447−}
448−
449−pub fn parse_keymaps(toml_keymaps: &HashMap<String, HashMap<String, String>>) -> Result<Keymaps> {
450− let mut keymaps = Keymaps::default();
451−
452− for (mode, map) in toml_keymaps {
453− let mode = Mode::from_str(&mode)?;
454− for (key, command) in map {
455− let key = str::parse::<RepresentableKeyEvent>(&key)?;
456− let command = str::parse::<Command>(&command)?;
457− keymaps.0.get_mut(&mode).unwrap().0.insert(key.0, command);
458− }
459− }
460− Ok(keymaps)
461−}
462−
463−impl Deref for Keymap {
464− type Target = HashMap<KeyEvent, Command>;
465− fn deref(&self) -> &Self::Target {
466− &self.0
467− }
468−}
469−
470−impl Deref for Keymaps {
471− type Target = HashMap<Mode, Keymap>;
472− fn deref(&self) -> &Self::Target {
473− &self.0
474− }
475−}
476−
477−impl DerefMut for Keymap {
478− fn deref_mut(&mut self) -> &mut Self::Target {
479− &mut self.0
480− }
481−}
482−
483−impl DerefMut for Keymaps {
484− fn deref_mut(&mut self) -> &mut Self::Target {
485− &mut self.0
486− }
487−}
488−
489−#[cfg(test)]
490−mod test {
491− use crate::config::Config;
492−
493− use super::*;
494−
495− impl PartialEq for Command {
496− fn eq(&self, other: &Self) -> bool {
497− self.name() == other.name()
498− }
499− }
500−
501− #[test]
502− fn parsing_keymaps_config_file() {
503− let sample_keymaps = r#"
504− [keys.insert]
505− y = "move_line_down"
506− S-C-a = "delete_selection"
507−
508− [keys.normal]
509− A-F12 = "move_next_word_end"
510− "#;
511−
512− let config: Config = toml::from_str(sample_keymaps).unwrap();
513− assert_eq!(
514− *config
515− .keymaps
516− .0
517− .get(&Mode::Insert)
518− .unwrap()
519− .0
520− .get(&KeyEvent {
521− code: KeyCode::Char('y'),
522− modifiers: KeyModifiers::NONE
523− })
524− .unwrap(),
525− Command::move_line_down
526− );
527− assert_eq!(
528− *config
529− .keymaps
530− .0
531− .get(&Mode::Insert)
532− .unwrap()
533− .0
534− .get(&KeyEvent {
535− code: KeyCode::Char('a'),
536− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
537− })
538− .unwrap(),
539− Command::delete_selection
540− );
541− assert_eq!(
542− *config
543− .keymaps
544− .0
545− .get(&Mode::Normal)
546− .unwrap()
547− .0
548− .get(&KeyEvent {
549− code: KeyCode::F(12),
550− modifiers: KeyModifiers::ALT
551− })
552− .unwrap(),
553− Command::move_next_word_end
554− );
555− }
556−
557− #[test]
558− fn parsing_unmodified_keys() {
559− assert_eq!(
560− str::parse::<RepresentableKeyEvent>("backspace").unwrap(),
561− RepresentableKeyEvent(KeyEvent {
562− code: KeyCode::Backspace,
563− modifiers: KeyModifiers::NONE
564− })
565− );
566−
567− assert_eq!(
568− str::parse::<RepresentableKeyEvent>("left").unwrap(),
569− RepresentableKeyEvent(KeyEvent {
570− code: KeyCode::Left,
571− modifiers: KeyModifiers::NONE
572− })
573− );
574−
575− assert_eq!(
576− str::parse::<RepresentableKeyEvent>(",").unwrap(),
577− RepresentableKeyEvent(KeyEvent {
578− code: KeyCode::Char(','),
579− modifiers: KeyModifiers::NONE
580− })
581− );
582−
583− assert_eq!(
584− str::parse::<RepresentableKeyEvent>("w").unwrap(),
585− RepresentableKeyEvent(KeyEvent {
586− code: KeyCode::Char('w'),
587− modifiers: KeyModifiers::NONE
588− })
589− );
590−
591− assert_eq!(
592− str::parse::<RepresentableKeyEvent>("F12").unwrap(),
593− RepresentableKeyEvent(KeyEvent {
594− code: KeyCode::F(12),
595− modifiers: KeyModifiers::NONE
596− })
597− );
598− }
599−
600− fn parsing_modified_keys() {
601− assert_eq!(
602− str::parse::<RepresentableKeyEvent>("S-minus").unwrap(),
603− RepresentableKeyEvent(KeyEvent {
604− code: KeyCode::Char('-'),
605− modifiers: KeyModifiers::SHIFT
606− })
607− );
608−
609− assert_eq!(
610− str::parse::<RepresentableKeyEvent>("C-A-S-F12").unwrap(),
611− RepresentableKeyEvent(KeyEvent {
612− code: KeyCode::F(12),
613− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
614− })
615− );
616−
617− assert_eq!(
618− str::parse::<RepresentableKeyEvent>("S-C-2").unwrap(),
619− RepresentableKeyEvent(KeyEvent {
620− code: KeyCode::F(2),
621− modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
622− })
623− );
624− }
625−
626− #[test]
627− fn parsing_nonsensical_keys_fails() {
628− assert!(str::parse::<RepresentableKeyEvent>("F13").is_err());
629− assert!(str::parse::<RepresentableKeyEvent>("F0").is_err());
630− assert!(str::parse::<RepresentableKeyEvent>("aaa").is_err());
631− assert!(str::parse::<RepresentableKeyEvent>("S-S-a").is_err());
632− assert!(str::parse::<RepresentableKeyEvent>("C-A-S-C-1").is_err());
633− assert!(str::parse::<RepresentableKeyEvent>("FU").is_err());
634− assert!(str::parse::<RepresentableKeyEvent>("123").is_err());
635− assert!(str::parse::<RepresentableKeyEvent>("S--").is_err());
636− }
637−}
helix-term/src/main.rs+6−8
@@ -1,10 +1,9 @@
1+use anyhow::{Context, Error, Result};
12use helix_term::application::Application;
23use helix_term::args::Args;
34use helix_term::config::Config;
45use std::path::PathBuf;
56
6−use anyhow::{Context, Result};
7−
87fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
98 let mut base_config = fern::Dispatch::new();
109
@@ -89,12 +88,11 @@ FLAGS:
8988 std::fs::create_dir_all(&conf_dir).ok();
9089 }
9190
92− let config = std::fs::read_to_string(conf_dir.join("config.toml"))
93− .ok()
94− .map(|s| toml::from_str(&s))
95− .transpose()?
96− .or_else(|| Some(Config::default()))
97− .unwrap();
91+ let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
92+ Ok(config) => toml::from_str(&config)?,
93+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
94+ Err(err) => return Err(Error::new(err)),
95+ };
9896
9997 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
10098
helix-term/src/ui/editor.rs+4−2
@@ -11,12 +11,13 @@ use helix_core::{
1111 syntax::{self, HighlightEvent},
1212 Position, Range,
1313};
14+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
1415use helix_view::{document::Mode, Document, Editor, Theme, View};
1516use std::borrow::Cow;
1617
1718use crossterm::{
1819 cursor,
19− event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
20+ event::{read, Event, EventStream},
2021};
2122use tui::{
2223 backend::CrosstermBackend,
@@ -607,7 +608,8 @@ impl Component for EditorView {
607608 cx.editor.resize(Rect::new(0, 0, width, height - 1));
608609 EventResult::Consumed(None)
609610 }
610− Event::Key(mut key) => {
611+ Event::Key(key) => {
612+ let mut key = KeyEvent::from(key);
611613 canonicalize_key(&mut key);
612614 // clear status
613615 cx.editor.status_msg = None;
helix-view/src/document.rs+36−25
@@ -1,5 +1,7 @@
11use anyhow::{anyhow, Context, Error};
2+use serde::de::{self, Deserialize, Deserializer};
23use std::cell::Cell;
4+use std::collections::HashMap;
35use std::fmt::Display;
46use std::future::Future;
57use std::path::{Component, Path, PathBuf};
@@ -15,8 +17,6 @@ use helix_core::{
1517
1618use crate::{DocumentId, ViewId};
1719
18−use std::collections::HashMap;
19−
2020#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2121pub enum Mode {
2222 Normal,
@@ -24,6 +24,40 @@ pub enum Mode {
2424 Insert,
2525}
2626
27+impl Display for Mode {
28+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29+ match self {
30+ Mode::Normal => f.write_str("normal"),
31+ Mode::Select => f.write_str("select"),
32+ Mode::Insert => f.write_str("insert"),
33+ }
34+ }
35+}
36+
37+impl FromStr for Mode {
38+ type Err = Error;
39+
40+ fn from_str(s: &str) -> Result<Self, Self::Err> {
41+ match s {
42+ "normal" => Ok(Mode::Normal),
43+ "select" => Ok(Mode::Select),
44+ "insert" => Ok(Mode::Insert),
45+ _ => Err(anyhow!("Invalid mode '{}'", s)),
46+ }
47+ }
48+}
49+
50+// toml deserializer doesn't seem to recognize string as enum
51+impl<'de> Deserialize<'de> for Mode {
52+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53+ where
54+ D: Deserializer<'de>,
55+ {
56+ let s = String::deserialize(deserializer)?;
57+ s.parse().map_err(de::Error::custom)
58+ }
59+}
60+
2761#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2862pub enum IndentStyle {
2963 Tabs,
@@ -88,29 +122,6 @@ impl fmt::Debug for Document {
88122 }
89123}
90124
91−impl Display for Mode {
92− fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93− match self {
94− Mode::Normal => f.write_str("normal"),
95− Mode::Select => f.write_str("select"),
96− Mode::Insert => f.write_str("insert"),
97− }
98− }
99−}
100−
101−impl FromStr for Mode {
102− type Err = Error;
103−
104− fn from_str(s: &str) -> Result<Self, Self::Err> {
105− match s {
106− "normal" => Ok(Mode::Normal),
107− "select" => Ok(Mode::Select),
108− "insert" => Ok(Mode::Insert),
109− _ => Err(anyhow!("Invalid mode '{}'", s)),
110− }
111− }
112−}
113−
114125/// Like std::mem::replace() except it allows the replacement value to be mapped from the
115126/// original value.
116127fn take_with<T, F>(mut_ref: &mut T, closure: F)
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
@@ -3,14 +3,16 @@ pub mod macros;
33
44pub mod document;
55pub mod editor;
6+pub mod input;
67pub mod register_selection;
78pub mod theme;
89pub mod tree;
910pub mod view;
1011
11−use slotmap::new_key_type;
12−new_key_type! { pub struct DocumentId; }
13−new_key_type! { pub struct ViewId; }
12+slotmap::new_key_type! {
13+ pub struct DocumentId;
14+ pub struct ViewId;
15+}
1416
1517pub use document::Document;
1618pub use editor::Editor;