From d3c8b3f52c6bc4077d719a636897f3eba7e7e04a Mon Sep 17 00:00:00 2001 From: Ken Micklas Date: Sun, 8 Oct 2023 10:38:09 +0100 Subject: [PATCH] Handle suffixes of cancelled keymaps in insert mode --- helix-term/src/ui/editor.rs | 48 ++++++++++++------- helix-term/tests/integration.rs | 1 + helix-term/tests/test/helpers.rs | 2 - helix-term/tests/test/insert_keymap_suffix.rs | 36 ++++++++++++++ 4 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 helix-term/tests/test/insert_keymap_suffix.rs diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dd6fff087bee3..0328e1d81b29b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -29,7 +29,7 @@ use helix_view::{ keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; -use std::{mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc, sync::Arc}; +use std::{collections::VecDeque, mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc, sync::Arc}; use tui::{buffer::Buffer as Surface, text::Span}; @@ -857,28 +857,40 @@ impl EditorView { } fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) { - if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { - match keyresult { - KeymapResult::NotFound => { - if let Some(ch) = event.char() { - commands::insert::insert_char(cx, ch) + let mut queue = VecDeque::from([event]); + + while let Some(event) = queue.pop_front() { + if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { + match keyresult { + KeymapResult::NotFound => { + if let Some(ch) = event.char() { + commands::insert::insert_char(cx, ch) + } } - } - KeymapResult::Cancelled(pending) => { - for ev in pending { - match ev.char() { - Some(ch) => commands::insert::insert_char(cx, ch), - None => { - if let KeymapResult::Matched(command) = - self.keymaps.get(Mode::Insert, ev) - { - command.execute(cx); - } + KeymapResult::Cancelled(pending) => { + let mut pending = pending.into_iter(); + if let Some(first) = pending.next() { + // Note that since this is the first pending key, we know + // it can't map to a command by itself. + match first.char() { + // The first key is both the start of a menu and a regular + // insert key. The user may have intended to type it as an + // insert, and then execute the remaining suffix of keys. + Some(ch) => commands::insert::insert_char(cx, ch), + // If the first key is not a character to insert, then we + // assume the user intended to enter a command menu, so we + // should just discard pending keys if they don't match. + None => continue, } } + + // Sadly VecDeque has no extend_front method. + for event in pending.rev() { + queue.push_front(event); + } } + _ => unreachable!(), } - _ => unreachable!(), } } } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 35214bcb8011d..6240e698878ce 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -18,6 +18,7 @@ mod test { mod auto_indent; mod auto_pairs; mod commands; + mod insert_keymap_suffix; mod languages; mod movement; mod prompt; diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index e6762baf9c6d6..6105c4d9743f9 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -300,8 +300,6 @@ impl AppBuilder { self } - // Remove this attribute once `with_config` is used in a test: - #[allow(dead_code)] pub fn with_config(mut self, mut config: Config) -> Self { let keys = replace(&mut config.keys, helix_term::keymap::default()); merge_keys(&mut config.keys, keys); diff --git a/helix-term/tests/test/insert_keymap_suffix.rs b/helix-term/tests/test/insert_keymap_suffix.rs new file mode 100644 index 0000000000000..8315d5677becf --- /dev/null +++ b/helix-term/tests/test/insert_keymap_suffix.rs @@ -0,0 +1,36 @@ +use super::*; + +#[tokio::test(flavor = "multi_thread")] +async fn insert_keymap_suffix() -> anyhow::Result<()> { + test_with_config( + AppBuilder::new().with_config(config()), + ("#[|]#", "iselffd", "self#[|]#"), + ) + .await?; + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn insert_keymap_suffix_non_char() -> anyhow::Result<()> { + test_with_config( + AppBuilder::new().with_config(config()), + ("#[|]#", "iua", "a#[|]#"), + ) + .await?; + + Ok(()) +} + +fn config() -> Config { + let config = r#" + [keys.insert] + f.d = "normal_mode" + F1.j = "insert_newline" + "#; + Config::load( + Ok(config.to_owned()), + Err(helix_term::config::ConfigLoadError::default()), + ) + .unwrap() +}