Repeat insert command (.).

This commit is contained in:
Blaž Hrastnik 2021-03-30 18:19:27 +09:00
parent ebfd67ac6a
commit 88bb7a1f38
3 changed files with 101 additions and 73 deletions

View file

@ -722,7 +722,7 @@ pub fn delete_selection(cx: &mut Context) {
pub fn change_selection(cx: &mut Context) { pub fn change_selection(cx: &mut Context) {
let doc = cx.doc(); let doc = cx.doc();
_delete_selection(doc); _delete_selection(doc);
insert_mode(cx); enter_insert_mode(doc);
} }
pub fn collapse_selection(cx: &mut Context) { pub fn collapse_selection(cx: &mut Context) {

View file

@ -88,43 +88,44 @@ use std::collections::HashMap;
// } // }
// #[cfg(feature = "term")] // #[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<Key, Command>; pub type Keymap = HashMap<KeyEvent, Command>;
pub type Keymaps = HashMap<Mode, Keymap>; pub type Keymaps = HashMap<Mode, Keymap>;
#[macro_export]
macro_rules! key { macro_rules! key {
($ch:expr) => { ($($ch:tt)*) => {
Key { KeyEvent {
code: KeyCode::Char($ch), code: KeyCode::Char($($ch)*),
modifiers: Modifiers::NONE, modifiers: KeyModifiers::NONE,
} }
}; };
} }
macro_rules! shift { macro_rules! shift {
($ch:expr) => { ($($ch:tt)*) => {
Key { KeyEvent {
code: KeyCode::Char($ch), code: KeyCode::Char($($ch)*),
modifiers: Modifiers::SHIFT, modifiers: KeyModifiers::SHIFT,
} }
}; };
} }
macro_rules! ctrl { macro_rules! ctrl {
($ch:expr) => { ($($ch:tt)*) => {
Key { KeyEvent {
code: KeyCode::Char($ch), code: KeyCode::Char($($ch)*),
modifiers: Modifiers::CONTROL, modifiers: KeyModifiers::CONTROL,
} }
}; };
} }
macro_rules! alt { macro_rules! alt {
($ch:expr) => { ($($ch:tt)*) => {
Key { KeyEvent {
code: KeyCode::Char($ch), code: KeyCode::Char($($ch)*),
modifiers: Modifiers::ALT, modifiers: KeyModifiers::ALT,
} }
}; };
} }
@ -228,26 +229,26 @@ pub fn default() -> Keymaps {
// C / altC = copy (repeat) selections on prev/next lines // C / altC = copy (repeat) selections on prev/next lines
Key { KeyEvent {
code: KeyCode::Esc, code: KeyCode::Esc,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::normal_mode, } => commands::normal_mode,
Key { KeyEvent {
code: KeyCode::PageUp, code: KeyCode::PageUp,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::page_up, } => commands::page_up,
Key { KeyEvent {
code: KeyCode::PageDown, code: KeyCode::PageDown,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::page_down, } => commands::page_down,
ctrl!('u') => commands::half_page_up, ctrl!('u') => commands::half_page_up,
ctrl!('d') => commands::half_page_down, ctrl!('d') => commands::half_page_down,
ctrl!('p') => commands::file_picker, ctrl!('p') => commands::file_picker,
ctrl!('b') => commands::buffer_picker, ctrl!('b') => commands::buffer_picker,
Key { KeyEvent {
code: KeyCode::Tab, code: KeyCode::Tab,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::next_view, } => commands::next_view,
// move under <space>c // move under <space>c
@ -280,9 +281,9 @@ pub fn default() -> Keymaps {
shift!('T') => commands::extend_till_prev_char, shift!('T') => commands::extend_till_prev_char,
shift!('F') => commands::extend_prev_char, shift!('F') => commands::extend_prev_char,
Key { KeyEvent {
code: KeyCode::Esc, code: KeyCode::Esc,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::exit_select_mode as Command, } => commands::exit_select_mode as Command,
) )
.into_iter(), .into_iter(),
@ -294,25 +295,25 @@ pub fn default() -> Keymaps {
Mode::Normal => normal, Mode::Normal => normal,
Mode::Select => select, Mode::Select => select,
Mode::Insert => hashmap!( Mode::Insert => hashmap!(
Key { KeyEvent {
code: KeyCode::Esc, code: KeyCode::Esc,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::normal_mode as Command, } => commands::normal_mode as Command,
Key { KeyEvent {
code: KeyCode::Backspace, code: KeyCode::Backspace,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::insert::delete_char_backward, } => commands::insert::delete_char_backward,
Key { KeyEvent {
code: KeyCode::Delete, code: KeyCode::Delete,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::insert::delete_char_forward, } => commands::insert::delete_char_forward,
Key { KeyEvent {
code: KeyCode::Enter, code: KeyCode::Enter,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::insert::insert_newline, } => commands::insert::insert_newline,
Key { KeyEvent {
code: KeyCode::Tab, code: KeyCode::Tab,
modifiers: Modifiers::NONE modifiers: KeyModifiers::NONE
} => commands::insert::insert_tab, } => commands::insert::insert_tab,
ctrl!('x') => commands::completion, ctrl!('x') => commands::completion,

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
commands, commands,
compositor::{Component, Compositor, Context, EventResult}, compositor::{Component, Compositor, Context, EventResult},
key,
keymap::{self, Keymaps}, keymap::{self, Keymaps},
ui::text_color, ui::text_color,
}; };
@ -27,6 +28,7 @@ pub struct EditorView {
keymap: Keymaps, keymap: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>, on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
status_msg: Option<String>, status_msg: Option<String>,
last_insert: (commands::Command, Vec<KeyEvent>),
} }
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
@ -37,6 +39,7 @@ impl EditorView {
keymap: keymap::default(), keymap: keymap::default(),
on_next_key: None, on_next_key: None,
status_msg: None, status_msg: None,
last_insert: (commands::normal_mode, Vec::new()),
} }
} }
@ -429,6 +432,48 @@ impl EditorView {
text_color, 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 { impl Component for EditorView {
@ -461,50 +506,32 @@ impl Component for EditorView {
} else { } else {
match mode { match mode {
Mode::Insert => { Mode::Insert => {
if let Some(command) = self.keymap[&Mode::Insert].get(&event) { // record last_insert key
command(&mut cxt); self.last_insert.1.push(event);
} 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) { self.insert_mode(&mut cxt, event)
command(&mut cxt); }
mode => self.command_mode(mode, &mut cxt, event),
} }
} }
}
}
}
}
self.on_next_key = cxt.on_next_key_callback.take(); self.on_next_key = cxt.on_next_key_callback.take();
self.status_msg = cxt.status_msg.take(); self.status_msg = cxt.status_msg.take();
// appease borrowck // appease borrowck
let callback = cxt.callback.take(); let callback = cxt.callback.take();
drop(cxt);
cx.editor.ensure_cursor_in_view(cx.editor.tree.focus); 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) EventResult::Consumed(callback)
} }
Event::Mouse(_) => EventResult::Ignored, Event::Mouse(_) => EventResult::Ignored,