Repeat insert command (.).
This commit is contained in:
parent
ebfd67ac6a
commit
88bb7a1f38
3 changed files with 101 additions and 73 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue