From 08ec9d5adaa6e4586a64c9b8f202f3e684e29ee5 Mon Sep 17 00:00:00 2001 From: jyn Date: Sat, 3 Aug 2024 00:14:48 -0400 Subject: [PATCH] add `insert_character_interactive` command this command inserts a character of the user's choice underneath the current cursor, without exiting normal mode. multiple cursors are handled correctly. i chose `C-y` for the default mapping because it doesn't conflict with anything else. i would have preferred `C-i` (i for insert), but that conflicts with `jump_forward`; and additionally most terminals do not distinguish `C-i` from `tab`, so it's not a good choice for the default. this command does not have precedence in vim or kakoune to my knowledge, but at least vim allows replicating it with remap command (https://superuser.com/questions/581572/insert-single-character-in-vim), while helix currently does not, since there's no way to say "a character entered by the user". --- helix-term/src/commands.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e9d3a28ff..995ca9e2a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -397,6 +397,7 @@ impl MappableCommand { smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.", insert_tab, "Insert tab char", insert_newline, "Insert newline char", + insert_char_interactive, "Insert an interactively-chosen char", delete_char_backward, "Delete previous char", delete_char_forward, "Delete next char", delete_word_backward, "Delete previous word", @@ -3727,7 +3728,7 @@ fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range { } pub mod insert { - use crate::events::PostInsertChar; + use crate::{events::PostInsertChar, key}; use super::*; pub type Hook = fn(&Rope, &Selection, char) -> Option; @@ -3802,11 +3803,15 @@ pub mod insert { } pub fn insert_tab(cx: &mut Context) { + insert_tab_impl(cx, 1) + } + + fn insert_tab_impl(cx: &mut Context, count: usize) { let (view, doc) = current!(cx.editor); // TODO: round out to nearest indentation level (for example a line with 3 spaces should // indent by one to reach 4 spaces). - let indent = Tendril::from(doc.indent_style.as_str()); + let indent = Tendril::from(doc.indent_style.as_str().repeat(count)); let transaction = Transaction::insert( doc.text(), &doc.selection(view.id).clone().cursors(doc.text().slice(..)), @@ -3815,6 +3820,34 @@ pub mod insert { doc.apply(&transaction, view.id); } + pub fn insert_char_interactive(cx: &mut Context) { + let count = cx.count(); + + // need to wait for next key + cx.on_next_key(move |cx, event| { + match event { + KeyEvent { + code: KeyCode::Char(ch), + .. + } => { + for _ in 0..count { + insert::insert_char(cx, ch) + } + } + key!(Enter) => { + if count != 1 { + cx.editor + .set_error("inserting multiple newlines not yet supported"); + return; + } + insert_newline(cx) + } + key!(Tab) => insert_tab_impl(cx, count), + _ => (), + }; + }); + } + pub fn insert_newline(cx: &mut Context) { let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..);