From 1f613df968c83fe78aa291fea6ea506003977114 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 2 Oct 2022 15:32:09 -0500 Subject: [PATCH] Automatically track pseudo-pending text This change automatically tracks pending text for for commands which use on-next-key callbacks. For example, `t` will await the next key event and "t" will be shown in the bottom right-hand corner to show that we're in a pending state. Previously, the text for these on-next-key commands needed to be hard-coded into the command definition which had some drawbacks: * It was easy to forget to write and clear the pending text. * If a command was remapped in a custom config, the pending text would still show the old key. With this change, pending text is automatically tracked based on the key events that lead to the command being executed. This works even when the command is remapped in config and when the on-next-key callback is nested under some key sequence (for example `mi`). --- helix-term/src/commands.rs | 78 ++++++++++++++++--------------------- helix-term/src/ui/editor.rs | 12 +++++- helix-view/src/editor.rs | 2 - 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 264ab5bb7fa4..6fdd03169bc3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1086,27 +1086,18 @@ fn extend_next_long_word_end(cx: &mut Context) { extend_word_impl(cx, movement::move_next_long_word_end) } -fn will_find_char( - cx: &mut Context, - search_fn: F, - inclusive: bool, - extend: bool, - pseudo_pending: &str, -) where +fn will_find_char(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool) +where F: Fn(RopeSlice, char, usize, usize, bool) -> Option + 'static, { // TODO: count is reset to 1 before next key so we move it into the closure here. // Would be nice to carry over. let count = cx.count(); - cx.editor.pseudo_pending = Some(pseudo_pending.to_string()); - // need to wait for next key // TODO: should this be done by grapheme rather than char? For example, // we can't properly handle the line-ending CRLF case here in terms of char. cx.on_next_key(move |cx, event| { - cx.editor.pseudo_pending = None; - let ch = match event { KeyEvent { code: KeyCode::Enter, @@ -1209,35 +1200,35 @@ fn find_prev_char_impl( } fn find_till_char(cx: &mut Context) { - will_find_char(cx, find_next_char_impl, false, false, "t") + will_find_char(cx, find_next_char_impl, false, false) } fn find_next_char(cx: &mut Context) { - will_find_char(cx, find_next_char_impl, true, false, "f") + will_find_char(cx, find_next_char_impl, true, false) } fn extend_till_char(cx: &mut Context) { - will_find_char(cx, find_next_char_impl, false, true, "t") + will_find_char(cx, find_next_char_impl, false, true) } fn extend_next_char(cx: &mut Context) { - will_find_char(cx, find_next_char_impl, true, true, "f") + will_find_char(cx, find_next_char_impl, true, true) } fn till_prev_char(cx: &mut Context) { - will_find_char(cx, find_prev_char_impl, false, false, "T") + will_find_char(cx, find_prev_char_impl, false, false) } fn find_prev_char(cx: &mut Context) { - will_find_char(cx, find_prev_char_impl, true, false, "F") + will_find_char(cx, find_prev_char_impl, true, false) } fn extend_till_prev_char(cx: &mut Context) { - will_find_char(cx, find_prev_char_impl, false, true, "T") + will_find_char(cx, find_prev_char_impl, false, true) } fn extend_prev_char(cx: &mut Context) { - will_find_char(cx, find_prev_char_impl, true, true, "F") + will_find_char(cx, find_prev_char_impl, true, true) } fn repeat_last_motion(cx: &mut Context) { @@ -4350,7 +4341,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { cx.on_next_key(move |cx, event| { cx.editor.autoinfo = None; - cx.editor.pseudo_pending = None; if let Some(ch) = event.char() { let textobject = move |editor: &mut Editor| { let (view, doc) = current!(editor); @@ -4399,33 +4389,31 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { } }); - if let Some((title, abbrev)) = match objtype { - textobject::TextObject::Inside => Some(("Match inside", "mi")), - textobject::TextObject::Around => Some(("Match around", "ma")), + let title = match objtype { + textobject::TextObject::Inside => "Match inside", + textobject::TextObject::Around => "Match around", _ => return, - } { - let help_text = [ - ("w", "Word"), - ("W", "WORD"), - ("p", "Paragraph"), - ("c", "Class (tree-sitter)"), - ("f", "Function (tree-sitter)"), - ("a", "Argument/parameter (tree-sitter)"), - ("o", "Comment (tree-sitter)"), - ("t", "Test (tree-sitter)"), - ("m", "Closest surrounding pair to cursor"), - (" ", "... or any character acting as a pair"), - ]; - - cx.editor.autoinfo = Some(Info::new( - title, - help_text - .into_iter() - .map(|(col1, col2)| (col1.to_string(), col2.to_string())) - .collect(), - )); - cx.editor.pseudo_pending = Some(abbrev.to_string()); }; + let help_text = [ + ("w", "Word"), + ("W", "WORD"), + ("p", "Paragraph"), + ("c", "Class (tree-sitter)"), + ("f", "Function (tree-sitter)"), + ("a", "Argument/parameter (tree-sitter)"), + ("o", "Comment (tree-sitter)"), + ("t", "Test (tree-sitter)"), + ("m", "Closest surrounding pair to cursor"), + (" ", "... or any character acting as a pair"), + ]; + + cx.editor.autoinfo = Some(Info::new( + title, + help_text + .into_iter() + .map(|(col1, col2)| (col1.to_string(), col2.to_string())) + .collect(), + )); } fn surround_add(cx: &mut Context) { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7cb29c3b1ecb..18cb38a5a546 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -33,6 +33,7 @@ use super::statusline; pub struct EditorView { pub keymaps: Keymaps, on_next_key: Option>, + pseudo_pending: Vec, last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, spinners: ProgressSpinners, @@ -56,6 +57,7 @@ impl EditorView { Self { keymaps, on_next_key: None, + pseudo_pending: Vec::new(), last_insert: (commands::MappableCommand::normal_mode, Vec::new()), completion: None, spinners: ProgressSpinners::default(), @@ -831,6 +833,7 @@ impl EditorView { event: KeyEvent, ) -> Option { let mut last_mode = mode; + self.pseudo_pending.extend(self.keymaps.pending()); let key_result = self.keymaps.get(mode, event); cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); @@ -1308,6 +1311,11 @@ impl Component for EditorView { } self.on_next_key = cx.on_next_key_callback.take(); + match self.on_next_key { + Some(_) => self.pseudo_pending.push(key), + None => self.pseudo_pending.clear(), + } + // appease borrowck let callback = cx.callback.take(); @@ -1408,8 +1416,8 @@ impl Component for EditorView { for key in self.keymaps.pending() { disp.push_str(&key.key_sequence_format()); } - if let Some(pseudo_pending) = &cx.editor.pseudo_pending { - disp.push_str(pseudo_pending.as_str()) + for key in &self.pseudo_pending { + disp.push_str(&key.key_sequence_format()); } let style = cx.editor.theme.get("ui.text"); let macro_width = if cx.editor.macro_recording.is_some() { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5eff998360ef..7909fc6989c2 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -652,7 +652,6 @@ pub struct Editor { pub idle_timer: Pin>, pub last_motion: Option, - pub pseudo_pending: Option, pub last_completion: Option, @@ -724,7 +723,6 @@ impl Editor { idle_timer: Box::pin(sleep(conf.idle_timeout)), last_motion: None, last_completion: None, - pseudo_pending: None, config, auto_pairs, exit_code: 0,