diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 166325b9..61c62251 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -722,7 +722,7 @@ pub fn delete_selection(cx: &mut Context) { pub fn change_selection(cx: &mut Context) { let doc = cx.doc(); _delete_selection(doc); - insert_mode(cx); + enter_insert_mode(doc); } pub fn collapse_selection(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d7a72377..eed6ee54 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -88,43 +88,44 @@ use std::collections::HashMap; // } // #[cfg(feature = "term")] -pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers}; +pub use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -pub type Keymap = HashMap; +pub type Keymap = HashMap; pub type Keymaps = HashMap; +#[macro_export] macro_rules! key { - ($ch:expr) => { - Key { - code: KeyCode::Char($ch), - modifiers: Modifiers::NONE, + ($($ch:tt)*) => { + KeyEvent { + code: KeyCode::Char($($ch)*), + modifiers: KeyModifiers::NONE, } }; } macro_rules! shift { - ($ch:expr) => { - Key { - code: KeyCode::Char($ch), - modifiers: Modifiers::SHIFT, + ($($ch:tt)*) => { + KeyEvent { + code: KeyCode::Char($($ch)*), + modifiers: KeyModifiers::SHIFT, } }; } macro_rules! ctrl { - ($ch:expr) => { - Key { - code: KeyCode::Char($ch), - modifiers: Modifiers::CONTROL, + ($($ch:tt)*) => { + KeyEvent { + code: KeyCode::Char($($ch)*), + modifiers: KeyModifiers::CONTROL, } }; } macro_rules! alt { - ($ch:expr) => { - Key { - code: KeyCode::Char($ch), - modifiers: Modifiers::ALT, + ($($ch:tt)*) => { + KeyEvent { + code: KeyCode::Char($($ch)*), + modifiers: KeyModifiers::ALT, } }; } @@ -228,26 +229,26 @@ pub fn default() -> Keymaps { // C / altC = copy (repeat) selections on prev/next lines - Key { + KeyEvent { code: KeyCode::Esc, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::normal_mode, - Key { + KeyEvent { code: KeyCode::PageUp, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::page_up, - Key { + KeyEvent { code: KeyCode::PageDown, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::page_down, ctrl!('u') => commands::half_page_up, ctrl!('d') => commands::half_page_down, ctrl!('p') => commands::file_picker, ctrl!('b') => commands::buffer_picker, - Key { + KeyEvent { code: KeyCode::Tab, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::next_view, // move under c @@ -280,9 +281,9 @@ pub fn default() -> Keymaps { shift!('T') => commands::extend_till_prev_char, shift!('F') => commands::extend_prev_char, - Key { + KeyEvent { code: KeyCode::Esc, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::exit_select_mode as Command, ) .into_iter(), @@ -294,25 +295,25 @@ pub fn default() -> Keymaps { Mode::Normal => normal, Mode::Select => select, Mode::Insert => hashmap!( - Key { + KeyEvent { code: KeyCode::Esc, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::normal_mode as Command, - Key { + KeyEvent { code: KeyCode::Backspace, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::insert::delete_char_backward, - Key { + KeyEvent { code: KeyCode::Delete, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::insert::delete_char_forward, - Key { + KeyEvent { code: KeyCode::Enter, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::insert::insert_newline, - Key { + KeyEvent { code: KeyCode::Tab, - modifiers: Modifiers::NONE + modifiers: KeyModifiers::NONE } => commands::insert::insert_tab, ctrl!('x') => commands::completion, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index b02bd981..1103d6f5 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1,6 +1,7 @@ use crate::{ commands, compositor::{Component, Compositor, Context, EventResult}, + key, keymap::{self, Keymaps}, ui::text_color, }; @@ -27,6 +28,7 @@ pub struct EditorView { keymap: Keymaps, on_next_key: Option>, status_msg: Option, + last_insert: (commands::Command, Vec), } const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter @@ -37,6 +39,7 @@ impl EditorView { keymap: keymap::default(), on_next_key: None, status_msg: None, + last_insert: (commands::normal_mode, Vec::new()), } } @@ -429,6 +432,48 @@ impl EditorView { text_color, ); } + + fn insert_mode(&self, cxt: &mut commands::Context, event: KeyEvent) { + if let Some(command) = self.keymap[&Mode::Insert].get(&event) { + command(cxt); + } else if let KeyEvent { + code: KeyCode::Char(ch), + .. + } = event + { + commands::insert::insert_char(cxt, ch); + } + } + + fn command_mode(&self, mode: Mode, cxt: &mut commands::Context, event: KeyEvent) { + match event { + // count handling + key!(i @ '0'..='9') => { + let i = i.to_digit(10).unwrap() as usize; + cxt.editor.count = Some(cxt.editor.count.map_or(i, |c| c * 10 + i)); + } + // special handling for repeat operator + key!('.') => { + // first execute whatever put us into insert mode + (self.last_insert.0)(cxt); + // then replay the inputs + for key in &self.last_insert.1 { + self.insert_mode(cxt, *key) + } + } + _ => { + // set the count + cxt.count = cxt.editor.count.take().unwrap_or(1); + // TODO: edge case: 0j -> reset to 1 + // if this fails, count was Some(0) + // debug_assert!(cxt.count != 0); + + if let Some(command) = self.keymap[&mode].get(&event) { + command(cxt); + } + } + } + } } impl Component for EditorView { @@ -461,50 +506,32 @@ impl Component for EditorView { } else { match mode { Mode::Insert => { - if let Some(command) = self.keymap[&Mode::Insert].get(&event) { - command(&mut cxt); - } else if let KeyEvent { - code: KeyCode::Char(c), - .. - } = event - { - commands::insert::insert_char(&mut cxt, c); - } - } - mode => { - match event { - KeyEvent { - code: KeyCode::Char(i @ '0'..='9'), - modifiers: KeyModifiers::NONE, - } => { - let i = i.to_digit(10).unwrap() as usize; - cxt.editor.count = - Some(cxt.editor.count.map_or(i, |c| c * 10 + i)); - } - _ => { - // set the count - cxt.count = cxt.editor.count.take().unwrap_or(1); - // TODO: edge case: 0j -> reset to 1 - // if this fails, count was Some(0) - // debug_assert!(cxt.count != 0); - - if let Some(command) = self.keymap[&mode].get(&event) { - command(&mut cxt); - } - } - } + // record last_insert key + self.last_insert.1.push(event); + + self.insert_mode(&mut cxt, event) } + mode => self.command_mode(mode, &mut cxt, event), } } - self.on_next_key = cxt.on_next_key_callback.take(); self.status_msg = cxt.status_msg.take(); - // appease borrowck let callback = cxt.callback.take(); - drop(cxt); + cx.editor.ensure_cursor_in_view(cx.editor.tree.focus); + if mode == Mode::Normal && cx.editor.document(id).unwrap().mode() == Mode::Insert { + // HAXX: if we just entered insert mode from normal, clear key buf + // and record the command that got us into this mode. + + // how we entered insert mode is important, and we should track that so + // we can repeat the side effect. + + self.last_insert.0 = self.keymap[&mode][&event]; + self.last_insert.1.clear(); + }; + EventResult::Consumed(callback) } Event::Mouse(_) => EventResult::Ignored,