mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
feat(tui): add vim named registers
This commit is contained in:
@@ -245,6 +245,8 @@ pub struct TuiVimNormalKeymap {
|
||||
pub yank_line: Option<KeybindingsSpec>,
|
||||
/// Paste after cursor (`p`).
|
||||
pub paste_after: Option<KeybindingsSpec>,
|
||||
/// Select a named register for the next Vim command (`"{register}`).
|
||||
pub select_register: Option<KeybindingsSpec>,
|
||||
/// Begin delete operator; next key selects motion (`d`).
|
||||
pub start_delete_operator: Option<KeybindingsSpec>,
|
||||
/// Begin yank operator; next key selects motion (`y`).
|
||||
|
||||
@@ -2845,6 +2845,7 @@
|
||||
"paste_after": null,
|
||||
"repeat_find": null,
|
||||
"repeat_find_reverse": null,
|
||||
"select_register": null,
|
||||
"start_change_operator": null,
|
||||
"start_delete_operator": null,
|
||||
"start_yank_operator": null,
|
||||
@@ -3544,6 +3545,7 @@
|
||||
"paste_after": null,
|
||||
"repeat_find": null,
|
||||
"repeat_find_reverse": null,
|
||||
"select_register": null,
|
||||
"start_change_operator": null,
|
||||
"start_delete_operator": null,
|
||||
"start_yank_operator": null,
|
||||
@@ -3996,6 +3998,14 @@
|
||||
],
|
||||
"description": "Repeat the most recent find/till motion in the opposite direction (`,`)."
|
||||
},
|
||||
"select_register": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Select a named register for the next Vim command (`\"{register}`)."
|
||||
},
|
||||
"start_change_operator": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ use ratatui::widgets::StatefulWidgetRef;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use std::cell::Ref;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use textwrap::Options;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
@@ -42,6 +43,7 @@ use self::vim::VimMode;
|
||||
use self::vim::VimMotion;
|
||||
use self::vim::VimOperator;
|
||||
use self::vim::VimPending;
|
||||
use self::vim::VimRegisterSelection;
|
||||
use self::vim::VimTextObjectScope;
|
||||
|
||||
const WORD_SEPARATORS: &str = "`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?";
|
||||
@@ -108,6 +110,8 @@ pub(crate) struct TextArea {
|
||||
next_element_id: u64,
|
||||
kill_buffer: String,
|
||||
kill_buffer_kind: KillBufferKind,
|
||||
vim_named_registers: BTreeMap<char, (String, KillBufferKind)>,
|
||||
vim_selected_register: Option<VimRegisterSelection>,
|
||||
vim_enabled: bool,
|
||||
vim_mode: VimMode,
|
||||
vim_pending: VimPending,
|
||||
@@ -152,6 +156,8 @@ impl TextArea {
|
||||
next_element_id: 1,
|
||||
kill_buffer: String::new(),
|
||||
kill_buffer_kind: KillBufferKind::Characterwise,
|
||||
vim_named_registers: BTreeMap::new(),
|
||||
vim_selected_register: None,
|
||||
vim_enabled: false,
|
||||
vim_mode: VimMode::Insert,
|
||||
vim_pending: VimPending::None,
|
||||
@@ -239,6 +245,7 @@ impl TextArea {
|
||||
self.vim_enabled = enabled;
|
||||
self.vim_pending = VimPending::None;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.vim_mode = if enabled {
|
||||
VimMode::Normal
|
||||
} else {
|
||||
@@ -287,6 +294,7 @@ impl TextArea {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
self.vim_pending = VimPending::None;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +309,7 @@ impl TextArea {
|
||||
self.vim_mode = VimMode::Normal;
|
||||
self.vim_pending = VimPending::None;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.preferred_col = None;
|
||||
}
|
||||
}
|
||||
@@ -660,6 +669,10 @@ impl TextArea {
|
||||
let pending = std::mem::replace(&mut self.vim_pending, VimPending::None);
|
||||
match pending {
|
||||
VimPending::None => {}
|
||||
VimPending::Register => {
|
||||
self.handle_vim_register_name(event);
|
||||
return;
|
||||
}
|
||||
VimPending::Operator(op) => {
|
||||
self.handle_vim_operator(op, event);
|
||||
return;
|
||||
@@ -674,13 +687,19 @@ impl TextArea {
|
||||
}
|
||||
}
|
||||
|
||||
if self.vim_normal_keymap.select_register.is_pressed(event) {
|
||||
self.vim_pending = VimPending::Register;
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.enter_insert.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.vim_mode = VimMode::Insert;
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.append_after_cursor.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
let next = self.next_atomic_boundary(self.cursor_pos);
|
||||
self.set_cursor(next);
|
||||
self.vim_mode = VimMode::Insert;
|
||||
@@ -688,18 +707,21 @@ impl TextArea {
|
||||
}
|
||||
if self.vim_normal_keymap.append_line_end.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.set_cursor(self.end_of_current_line());
|
||||
self.vim_mode = VimMode::Insert;
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.insert_line_start.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.set_cursor(self.first_non_blank_of_current_line());
|
||||
self.vim_mode = VimMode::Insert;
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.open_line_below.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
let eol = self.end_of_current_line();
|
||||
let insert_at = if eol < self.text.len() { eol + 1 } else { eol };
|
||||
self.insert_str_at(insert_at, "\n");
|
||||
@@ -714,6 +736,7 @@ impl TextArea {
|
||||
}
|
||||
if self.vim_normal_keymap.open_line_above.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
let bol = self.beginning_of_current_line();
|
||||
self.insert_str_at(bol, "\n");
|
||||
self.set_cursor(bol);
|
||||
@@ -785,6 +808,7 @@ impl TextArea {
|
||||
if self.vim_normal_keymap.delete_char.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
self.delete_forward_kill(count);
|
||||
self.clear_vim_register_selection();
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.delete_to_line_end.is_pressed(event) {
|
||||
@@ -800,22 +824,25 @@ impl TextArea {
|
||||
if self.vim_normal_keymap.substitute_char.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
self.delete_forward_kill(count);
|
||||
self.clear_vim_register_selection();
|
||||
self.vim_mode = VimMode::Insert;
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.substitute_line.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
self.vim_change_current_lines(count);
|
||||
self.clear_vim_register_selection();
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.yank_line.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
self.vim_yank_current_lines(count);
|
||||
self.clear_vim_register_selection();
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.paste_after.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
self.paste_after_cursor_counted(count);
|
||||
self.vim_paste_after_cursor_counted(count);
|
||||
return;
|
||||
}
|
||||
if self
|
||||
@@ -841,29 +868,35 @@ impl TextArea {
|
||||
if self.vim_normal_keymap.cancel_operator.is_pressed(event) {
|
||||
self.vim_pending = VimPending::None;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
return;
|
||||
}
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
fn handle_vim_operator(&mut self, op: VimOperator, event: KeyEvent) -> bool {
|
||||
if op == VimOperator::Delete && self.vim_operator_keymap.delete_line.is_pressed(event) {
|
||||
let count = self.take_vim_operator_count();
|
||||
self.vim_kill_current_lines(count);
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
if op == VimOperator::Yank && self.vim_operator_keymap.yank_line.is_pressed(event) {
|
||||
let count = self.take_vim_operator_count();
|
||||
self.vim_yank_current_lines(count);
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
if op == VimOperator::Change && self.vim_operator_keymap.change_line.is_pressed(event) {
|
||||
let count = self.take_vim_operator_count();
|
||||
self.vim_change_current_lines(count);
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
if self.vim_operator_keymap.cancel.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
if let Some(scope) = self.vim_text_object_scope_for_event(event) {
|
||||
@@ -901,6 +934,7 @@ impl TextArea {
|
||||
return true;
|
||||
}
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
false
|
||||
}
|
||||
|
||||
@@ -912,16 +946,19 @@ impl TextArea {
|
||||
) -> bool {
|
||||
if self.vim_text_object_keymap.cancel.is_pressed(event) {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
let Some(object) = self.vim_text_object_for_event(event) else {
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
return false;
|
||||
};
|
||||
let count = self.take_vim_operator_count();
|
||||
if let Some(range) = self.counted_text_object_range(object, scope, count) {
|
||||
self.apply_vim_operator_to_range(op, range);
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -985,6 +1022,7 @@ impl TextArea {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
fn range_for_change_motion(&mut self, motion: VimMotion, count: usize) -> Option<Range<usize>> {
|
||||
@@ -1010,6 +1048,7 @@ impl TextArea {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
}
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
fn range_for_motion(&mut self, motion: VimMotion, count: usize) -> Option<Range<usize>> {
|
||||
@@ -1254,25 +1293,25 @@ impl TextArea {
|
||||
}
|
||||
|
||||
fn store_kill_buffer(&mut self, text: String, kind: KillBufferKind) {
|
||||
self.write_selected_vim_register(&text, kind);
|
||||
self.kill_buffer = text;
|
||||
self.kill_buffer_kind = kind;
|
||||
}
|
||||
|
||||
fn paste_after_cursor(&mut self) {
|
||||
if self.kill_buffer.is_empty() {
|
||||
fn paste_text_after_cursor(&mut self, text: &str, kind: KillBufferKind) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.kill_buffer_kind == KillBufferKind::Linewise {
|
||||
self.paste_line_after_current_line();
|
||||
if kind == KillBufferKind::Linewise {
|
||||
self.paste_line_after_current_line(text);
|
||||
return;
|
||||
}
|
||||
let insert_at = self.next_atomic_boundary(self.cursor_pos);
|
||||
self.set_cursor(insert_at);
|
||||
let text = self.kill_buffer.clone();
|
||||
self.insert_str(&text);
|
||||
self.insert_str(text);
|
||||
}
|
||||
|
||||
fn paste_line_after_current_line(&mut self) {
|
||||
fn paste_line_after_current_line(&mut self, buffer: &str) {
|
||||
let eol = self.end_of_current_line();
|
||||
let insert_at = if eol < self.text.len() { eol + 1 } else { eol };
|
||||
let cursor = if eol < self.text.len() {
|
||||
@@ -1281,13 +1320,13 @@ impl TextArea {
|
||||
insert_at + 1
|
||||
};
|
||||
let text = if eol < self.text.len() {
|
||||
if self.kill_buffer.ends_with('\n') {
|
||||
self.kill_buffer.clone()
|
||||
if buffer.ends_with('\n') {
|
||||
buffer.to_string()
|
||||
} else {
|
||||
format!("{}\n", self.kill_buffer)
|
||||
format!("{buffer}\n")
|
||||
}
|
||||
} else {
|
||||
format!("\n{}", self.kill_buffer.trim_end_matches('\n'))
|
||||
format!("\n{}", buffer.trim_end_matches('\n'))
|
||||
};
|
||||
self.insert_str_at(insert_at, &text);
|
||||
self.set_cursor(cursor.min(self.text.len()));
|
||||
@@ -2748,6 +2787,77 @@ mod tests {
|
||||
assert_eq!(t.vim_mode_label(), Some("Insert"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_named_registers_write_and_paste_independently_of_unnamed() {
|
||||
let mut t = ta_with("one two three");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
assert_eq!(
|
||||
t.vim_named_registers.get(&'a'),
|
||||
Some(&("one ".to_string(), KillBufferKind::Characterwise))
|
||||
);
|
||||
|
||||
t.set_cursor(/*pos*/ "one ".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "one three");
|
||||
assert_eq!(t.kill_buffer, "two ");
|
||||
|
||||
t.set_cursor(t.text().len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "one threeone ");
|
||||
assert_eq!(t.kill_buffer, "two ");
|
||||
|
||||
let before_empty_paste = t.text().to_string();
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('z'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), before_empty_paste);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_uppercase_register_writes_append_and_change_captures_named_text() {
|
||||
let mut t = ta_with("one two three");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "one ".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT));
|
||||
t.input(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
assert_eq!(
|
||||
t.vim_named_registers.get(&'a'),
|
||||
Some(&("one two ".to_string(), KillBufferKind::Characterwise))
|
||||
);
|
||||
|
||||
t.set_text_clearing_elements("value");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('"'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "");
|
||||
assert_eq!(t.kill_buffer, "value");
|
||||
assert_eq!(
|
||||
t.vim_named_registers.get(&'c'),
|
||||
Some(&("value".to_string(), KillBufferKind::Characterwise))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_find_till_motions_repeat_and_stay_on_current_line() {
|
||||
let mut t = ta_with("ab,cd,ef\naz");
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::ops::Range;
|
||||
mod count;
|
||||
mod find;
|
||||
mod prose;
|
||||
mod register;
|
||||
mod tag;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -27,6 +28,7 @@ pub(super) enum VimOperator {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum VimPending {
|
||||
None,
|
||||
Register,
|
||||
Operator(VimOperator),
|
||||
TextObject {
|
||||
operator: VimOperator,
|
||||
@@ -38,6 +40,12 @@ pub(super) enum VimPending {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) struct VimRegisterSelection {
|
||||
pub(super) name: char,
|
||||
pub(super) append: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum VimMotion {
|
||||
Left,
|
||||
|
||||
@@ -55,6 +55,7 @@ impl TextArea {
|
||||
motion: VimMotion,
|
||||
count: usize,
|
||||
) {
|
||||
self.clear_vim_register_selection();
|
||||
if motion == VimMotion::LineEnd {
|
||||
for _ in 1..count {
|
||||
self.move_cursor_down();
|
||||
@@ -132,12 +133,6 @@ impl TextArea {
|
||||
self.vim_mode = super::VimMode::Insert;
|
||||
}
|
||||
|
||||
pub(in super::super) fn paste_after_cursor_counted(&mut self, count: usize) {
|
||||
for _ in 0..count {
|
||||
self.paste_after_cursor();
|
||||
}
|
||||
}
|
||||
|
||||
fn current_line_range_with_count(&self, count: usize) -> Range<usize> {
|
||||
let start = self.beginning_of_current_line();
|
||||
let mut end = self.current_line_range_with_newline().end;
|
||||
|
||||
@@ -107,6 +107,7 @@ impl TextArea {
|
||||
} else if let Some(target) = self.target_for_find(find, count) {
|
||||
self.set_cursor(target);
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
pub(in super::super) fn target_for_find(&self, find: VimFind, count: usize) -> Option<usize> {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use super::super::KillBufferKind;
|
||||
use super::super::TextArea;
|
||||
use super::VimRegisterSelection;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
|
||||
impl TextArea {
|
||||
pub(in super::super) fn handle_vim_register_name(&mut self, event: KeyEvent) -> bool {
|
||||
if self.vim_normal_keymap.cancel_operator.is_pressed(event) {
|
||||
self.clear_vim_register_selection();
|
||||
self.clear_vim_counts();
|
||||
return true;
|
||||
}
|
||||
let KeyCode::Char(name) = event.code else {
|
||||
self.clear_vim_register_selection();
|
||||
self.clear_vim_counts();
|
||||
return true;
|
||||
};
|
||||
if !name.is_ascii_alphabetic() {
|
||||
self.clear_vim_register_selection();
|
||||
self.clear_vim_counts();
|
||||
return true;
|
||||
}
|
||||
self.vim_selected_register = Some(VimRegisterSelection {
|
||||
name: name.to_ascii_lowercase(),
|
||||
append: name.is_ascii_uppercase(),
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub(in super::super) fn clear_vim_register_selection(&mut self) {
|
||||
self.vim_selected_register = None;
|
||||
}
|
||||
|
||||
pub(in super::super) fn write_selected_vim_register(
|
||||
&mut self,
|
||||
text: &str,
|
||||
kind: KillBufferKind,
|
||||
) {
|
||||
let Some(selection) = self.vim_selected_register.take() else {
|
||||
return;
|
||||
};
|
||||
if selection.append {
|
||||
let entry = self
|
||||
.vim_named_registers
|
||||
.entry(selection.name)
|
||||
.or_insert_with(|| (String::new(), kind));
|
||||
entry.0.push_str(text);
|
||||
entry.1 = kind;
|
||||
} else {
|
||||
self.vim_named_registers
|
||||
.insert(selection.name, (text.to_string(), kind));
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn vim_paste_after_cursor_counted(&mut self, count: usize) {
|
||||
let source = if let Some(selection) = self.vim_selected_register.take() {
|
||||
self.vim_named_registers
|
||||
.get(&selection.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| (String::new(), KillBufferKind::Characterwise))
|
||||
} else {
|
||||
(self.kill_buffer.clone(), self.kill_buffer_kind)
|
||||
};
|
||||
if source.0.is_empty() {
|
||||
return;
|
||||
}
|
||||
for _ in 0..count {
|
||||
self.paste_text_after_cursor(&source.0, source.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,7 @@ pub(crate) struct VimNormalKeymap {
|
||||
pub(crate) substitute_line: Vec<KeyBinding>,
|
||||
pub(crate) yank_line: Vec<KeyBinding>,
|
||||
pub(crate) paste_after: Vec<KeyBinding>,
|
||||
pub(crate) select_register: Vec<KeyBinding>,
|
||||
pub(crate) start_delete_operator: Vec<KeyBinding>,
|
||||
pub(crate) start_yank_operator: Vec<KeyBinding>,
|
||||
pub(crate) start_change_operator: Vec<KeyBinding>,
|
||||
@@ -517,6 +518,7 @@ impl RuntimeKeymap {
|
||||
substitute_line: resolve_local!(keymap, defaults, vim_normal, substitute_line),
|
||||
yank_line: resolve_local!(keymap, defaults, vim_normal, yank_line),
|
||||
paste_after: resolve_local!(keymap, defaults, vim_normal, paste_after),
|
||||
select_register: resolve_local!(keymap, defaults, vim_normal, select_register),
|
||||
start_delete_operator: resolve_local!(
|
||||
keymap,
|
||||
defaults,
|
||||
@@ -705,6 +707,11 @@ impl RuntimeKeymap {
|
||||
.repeat_find_reverse
|
||||
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
|
||||
}
|
||||
if keymap.vim_normal.select_register.is_none() {
|
||||
vim_normal
|
||||
.select_register
|
||||
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
|
||||
}
|
||||
|
||||
let mut vim_operator = VimOperatorKeymap {
|
||||
delete_line: resolve_local!(keymap, defaults, vim_operator, delete_line),
|
||||
@@ -1252,6 +1259,7 @@ impl RuntimeKeymap {
|
||||
],
|
||||
yank_line: default_bindings![shift(KeyCode::Char('y')), plain(KeyCode::Char('Y'))],
|
||||
paste_after: default_bindings![plain(KeyCode::Char('p'))],
|
||||
select_register: default_bindings![plain(KeyCode::Char('"'))],
|
||||
start_delete_operator: default_bindings![plain(KeyCode::Char('d'))],
|
||||
start_yank_operator: default_bindings![plain(KeyCode::Char('y'))],
|
||||
start_change_operator: default_bindings![plain(KeyCode::Char('c'))],
|
||||
@@ -1709,6 +1717,10 @@ impl RuntimeKeymap {
|
||||
),
|
||||
("yank_line", self.vim_normal.yank_line.as_slice()),
|
||||
("paste_after", self.vim_normal.paste_after.as_slice()),
|
||||
(
|
||||
"select_register",
|
||||
self.vim_normal.select_register.as_slice(),
|
||||
),
|
||||
(
|
||||
"start_delete_operator",
|
||||
self.vim_normal.start_delete_operator.as_slice(),
|
||||
@@ -2603,6 +2615,16 @@ mod tests {
|
||||
assert_eq!(runtime.vim_operator.till_forward, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_legacy_vim_bindings_prune_new_register_prefix_default() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
keymap.vim_normal.move_left = Some(one("\""));
|
||||
|
||||
let runtime = RuntimeKeymap::from_config(&keymap).expect("config should parse");
|
||||
|
||||
assert_eq!(runtime.vim_normal.select_register, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_legacy_vim_operator_bindings_prune_new_text_object_defaults() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
|
||||
@@ -146,6 +146,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("vim_normal", "Vim normal", "substitute_line", "Substitute the current line and enter insert mode."),
|
||||
action("vim_normal", "Vim normal", "yank_line", "Yank the entire line."),
|
||||
action("vim_normal", "Vim normal", "paste_after", "Paste after the cursor."),
|
||||
action("vim_normal", "Vim normal", "select_register", "Select a named register for the next command."),
|
||||
action("vim_normal", "Vim normal", "start_delete_operator", "Begin a delete operator and wait for a motion."),
|
||||
action("vim_normal", "Vim normal", "start_yank_operator", "Begin a yank operator and wait for a motion."),
|
||||
action("vim_normal", "Vim normal", "start_change_operator", "Begin a change operator and wait for a motion or text object."),
|
||||
@@ -305,6 +306,7 @@ pub(super) fn binding_slot<'a>(
|
||||
("vim_normal", "substitute_line") => Some(&mut keymap.vim_normal.substitute_line),
|
||||
("vim_normal", "yank_line") => Some(&mut keymap.vim_normal.yank_line),
|
||||
("vim_normal", "paste_after") => Some(&mut keymap.vim_normal.paste_after),
|
||||
("vim_normal", "select_register") => Some(&mut keymap.vim_normal.select_register),
|
||||
("vim_normal", "start_delete_operator") => Some(&mut keymap.vim_normal.start_delete_operator),
|
||||
("vim_normal", "start_yank_operator") => Some(&mut keymap.vim_normal.start_yank_operator),
|
||||
("vim_normal", "start_change_operator") => Some(&mut keymap.vim_normal.start_change_operator),
|
||||
@@ -446,6 +448,7 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("vim_normal", "substitute_line") => Some(runtime_keymap.vim_normal.substitute_line.as_slice()),
|
||||
("vim_normal", "yank_line") => Some(runtime_keymap.vim_normal.yank_line.as_slice()),
|
||||
("vim_normal", "paste_after") => Some(runtime_keymap.vim_normal.paste_after.as_slice()),
|
||||
("vim_normal", "select_register") => Some(runtime_keymap.vim_normal.select_register.as_slice()),
|
||||
("vim_normal", "start_delete_operator") => Some(runtime_keymap.vim_normal.start_delete_operator.as_slice()),
|
||||
("vim_normal", "start_yank_operator") => Some(runtime_keymap.vim_normal.start_yank_operator.as_slice()),
|
||||
("vim_normal", "start_change_operator") => Some(runtime_keymap.vim_normal.start_change_operator.as_slice()),
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
124 actions, 1 customized, 2 unbound.
|
||||
125 actions, 1 customized, 2 unbound.
|
||||
|
||||
[All] Common Customized (1) Unbound (2) App Composer Editor Vim Navigation Approval Debug
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
125 actions, 0 customized, 3 unbound.
|
||||
126 actions, 0 customized, 3 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (3) App Composer Editor Vim Navigation Approval Debug
|
||||
|
||||
|
||||
+2
-2
@@ -2,14 +2,14 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: snapshot
|
||||
---
|
||||
tab: All (124 selectable)
|
||||
tab: All (125 selectable)
|
||||
tab: Common (19 selectable)
|
||||
tab: Customized (0) (0 selectable)
|
||||
tab: Unbound (2) (2 selectable)
|
||||
tab: App (9 selectable)
|
||||
tab: Composer (5 selectable)
|
||||
tab: Editor (17 selectable)
|
||||
tab: Vim (65 selectable)
|
||||
tab: Vim (66 selectable)
|
||||
tab: Navigation (20 selectable)
|
||||
tab: Approval (8 selectable)
|
||||
tab: Debug (1 selectable)
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ expression: "render_picker(params, 78)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
124 actions, 0 customized, 2 unbound.
|
||||
125 actions, 0 customized, 2 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (2) App Composer Editor Vim
|
||||
Navigation Approval Debug
|
||||
|
||||
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
124 actions, 0 customized, 2 unbound.
|
||||
125 actions, 0 customized, 2 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (2) App Composer Editor Vim Navigation Approval Debug
|
||||
|
||||
|
||||
Reference in New Issue
Block a user