mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
feat(tui): add vim dot repeat
This commit is contained in:
@@ -231,6 +231,8 @@ pub struct TuiVimNormalKeymap {
|
||||
pub repeat_find: Option<KeybindingsSpec>,
|
||||
/// Repeat the most recent find/till motion in the opposite direction (`,`).
|
||||
pub repeat_find_reverse: Option<KeybindingsSpec>,
|
||||
/// Repeat the most recent text-changing Vim command (`.`).
|
||||
pub repeat_change: Option<KeybindingsSpec>,
|
||||
/// Delete character under cursor (`x`).
|
||||
pub delete_char: Option<KeybindingsSpec>,
|
||||
/// Delete from cursor to end of line (`D`).
|
||||
|
||||
@@ -2845,6 +2845,7 @@
|
||||
"open_line_above": null,
|
||||
"open_line_below": null,
|
||||
"paste_after": null,
|
||||
"repeat_change": null,
|
||||
"repeat_find": null,
|
||||
"repeat_find_reverse": null,
|
||||
"select_register": null,
|
||||
@@ -3553,6 +3554,7 @@
|
||||
"open_line_above": null,
|
||||
"open_line_below": null,
|
||||
"paste_after": null,
|
||||
"repeat_change": null,
|
||||
"repeat_find": null,
|
||||
"repeat_find_reverse": null,
|
||||
"select_register": null,
|
||||
@@ -4021,6 +4023,14 @@
|
||||
],
|
||||
"description": "Paste after cursor (`p`)."
|
||||
},
|
||||
"repeat_change": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Repeat the most recent text-changing Vim command (`.`)."
|
||||
},
|
||||
"repeat_find": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -40,11 +40,14 @@ use unicode_width::UnicodeWidthStr;
|
||||
|
||||
mod vim;
|
||||
use self::vim::VimFind;
|
||||
use self::vim::VimInsertRecording;
|
||||
use self::vim::VimMode;
|
||||
use self::vim::VimMotion;
|
||||
use self::vim::VimOperator;
|
||||
use self::vim::VimPending;
|
||||
use self::vim::VimRegisterSelection;
|
||||
use self::vim::VimRepeatChange;
|
||||
use self::vim::VimRepeatTarget;
|
||||
use self::vim::VimTextObjectScope;
|
||||
use self::vim::VimVisualKind;
|
||||
|
||||
@@ -121,6 +124,9 @@ pub(crate) struct TextArea {
|
||||
vim_operator_count: Option<usize>,
|
||||
vim_last_find: Option<VimFind>,
|
||||
vim_visual_anchor: Option<usize>,
|
||||
vim_last_change: Option<VimRepeatChange>,
|
||||
vim_insert_recording: Option<VimInsertRecording>,
|
||||
vim_replaying_change: bool,
|
||||
editor_keymap: EditorKeymap,
|
||||
vim_normal_keymap: VimNormalKeymap,
|
||||
vim_operator_keymap: VimOperatorKeymap,
|
||||
@@ -169,6 +175,9 @@ impl TextArea {
|
||||
vim_operator_count: None,
|
||||
vim_last_find: None,
|
||||
vim_visual_anchor: None,
|
||||
vim_last_change: None,
|
||||
vim_insert_recording: None,
|
||||
vim_replaying_change: false,
|
||||
editor_keymap: defaults.editor,
|
||||
vim_normal_keymap: defaults.vim_normal,
|
||||
vim_operator_keymap: defaults.vim_operator,
|
||||
@@ -211,6 +220,7 @@ impl TextArea {
|
||||
}
|
||||
|
||||
fn set_text_inner(&mut self, text: &str, elements: Option<&[UserTextElement]>) {
|
||||
self.invalidate_vim_repeat_change();
|
||||
// Stage 1: replace the raw text and keep the cursor in a safe byte range.
|
||||
self.text = text.to_string();
|
||||
self.cursor_pos = self.cursor_pos.clamp(0, self.text.len());
|
||||
@@ -255,6 +265,7 @@ impl TextArea {
|
||||
pub(crate) fn set_vim_enabled(&mut self, enabled: bool) {
|
||||
self.vim_enabled = enabled;
|
||||
self.vim_pending = VimPending::None;
|
||||
self.vim_insert_recording = None;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
self.clear_vim_visual_selection();
|
||||
@@ -671,15 +682,14 @@ impl TextArea {
|
||||
|
||||
fn handle_vim_insert(&mut self, event: KeyEvent) {
|
||||
if matches!(event.code, KeyCode::Esc) {
|
||||
let bol = self.beginning_of_current_line();
|
||||
if self.cursor_pos > bol {
|
||||
self.cursor_pos = self.prev_atomic_boundary(self.cursor_pos).max(bol);
|
||||
}
|
||||
self.enter_vim_normal_mode();
|
||||
self.exit_vim_insert_after_change();
|
||||
self.complete_vim_change_recording();
|
||||
return;
|
||||
}
|
||||
let edit = self.vim_insert_edit_for_event(event);
|
||||
let keymap = self.editor_keymap.clone();
|
||||
self.input_with_keymap(event, &keymap);
|
||||
self.record_vim_insert_edit(edit);
|
||||
}
|
||||
|
||||
fn handle_vim_normal(&mut self, event: KeyEvent) {
|
||||
@@ -836,9 +846,15 @@ impl TextArea {
|
||||
self.repeat_vim_find(/*operator*/ None, /*reverse*/ true, count);
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.repeat_change.is_pressed(event) {
|
||||
self.repeat_vim_change();
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.delete_char.is_pressed(event) {
|
||||
let count = self.take_vim_count();
|
||||
let previous_len = self.text.len();
|
||||
self.delete_forward_kill(count);
|
||||
self.record_vim_delete_if_changed(VimRepeatTarget::Characters(count), previous_len);
|
||||
self.clear_vim_register_selection();
|
||||
return;
|
||||
}
|
||||
@@ -857,12 +873,14 @@ impl TextArea {
|
||||
self.delete_forward_kill(count);
|
||||
self.clear_vim_register_selection();
|
||||
self.vim_mode = VimMode::Insert;
|
||||
self.begin_vim_change_recording(VimRepeatTarget::Characters(count));
|
||||
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();
|
||||
self.begin_vim_change_recording(VimRepeatTarget::Lines(count));
|
||||
return;
|
||||
}
|
||||
if self.vim_normal_keymap.yank_line.is_pressed(event) {
|
||||
@@ -909,7 +927,9 @@ impl TextArea {
|
||||
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();
|
||||
let previous_len = self.text.len();
|
||||
self.vim_kill_current_lines(count);
|
||||
self.record_vim_delete_if_changed(VimRepeatTarget::Lines(count), previous_len);
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
@@ -922,6 +942,7 @@ impl TextArea {
|
||||
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.begin_vim_change_recording(VimRepeatTarget::Lines(count));
|
||||
self.clear_vim_register_selection();
|
||||
return true;
|
||||
}
|
||||
@@ -987,7 +1008,12 @@ impl TextArea {
|
||||
};
|
||||
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);
|
||||
let target = VimRepeatTarget::TextObject {
|
||||
object,
|
||||
scope,
|
||||
count,
|
||||
};
|
||||
self.apply_vim_operator_to_range(op, range, Some(target));
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
true
|
||||
@@ -1033,6 +1059,7 @@ impl TextArea {
|
||||
}
|
||||
|
||||
fn apply_vim_operator(&mut self, op: VimOperator, motion: VimMotion, count: usize) {
|
||||
let previous_len = self.text.len();
|
||||
match op {
|
||||
VimOperator::Delete => {
|
||||
if let Some(range) = self.range_for_motion(motion, count) {
|
||||
@@ -1053,6 +1080,14 @@ impl TextArea {
|
||||
}
|
||||
}
|
||||
}
|
||||
let target = VimRepeatTarget::Motion { motion, count };
|
||||
match op {
|
||||
VimOperator::Delete => self.record_vim_delete_if_changed(target, previous_len),
|
||||
VimOperator::Change if self.vim_mode == VimMode::Insert => {
|
||||
self.begin_vim_change_recording(target);
|
||||
}
|
||||
VimOperator::Yank | VimOperator::Change => {}
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
@@ -1070,7 +1105,13 @@ impl TextArea {
|
||||
self.range_for_motion(motion, count)
|
||||
}
|
||||
|
||||
fn apply_vim_operator_to_range(&mut self, op: VimOperator, range: Range<usize>) {
|
||||
fn apply_vim_operator_to_range(
|
||||
&mut self,
|
||||
op: VimOperator,
|
||||
range: Range<usize>,
|
||||
repeat_target: Option<VimRepeatTarget>,
|
||||
) {
|
||||
let previous_len = self.text.len();
|
||||
match op {
|
||||
VimOperator::Delete => self.kill_range(range),
|
||||
VimOperator::Yank => self.yank_range(range),
|
||||
@@ -1079,6 +1120,15 @@ impl TextArea {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
}
|
||||
}
|
||||
if let Some(target) = repeat_target {
|
||||
match op {
|
||||
VimOperator::Delete => self.record_vim_delete_if_changed(target, previous_len),
|
||||
VimOperator::Change if self.vim_mode == VimMode::Insert => {
|
||||
self.begin_vim_change_recording(target);
|
||||
}
|
||||
VimOperator::Yank | VimOperator::Change => {}
|
||||
}
|
||||
}
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
@@ -1604,6 +1654,7 @@ impl TextArea {
|
||||
if start > end || end > self.text.len() {
|
||||
return false;
|
||||
}
|
||||
self.invalidate_vim_repeat_change();
|
||||
|
||||
let removed_len = end - start;
|
||||
let inserted_len = new.len();
|
||||
@@ -1655,6 +1706,7 @@ impl TextArea {
|
||||
}
|
||||
|
||||
pub fn insert_element(&mut self, text: &str) -> u64 {
|
||||
self.invalidate_vim_repeat_change();
|
||||
let start = self.clamp_pos_for_insertion(self.cursor_pos);
|
||||
self.insert_str_at(start, text);
|
||||
let end = start + text.len();
|
||||
@@ -1666,6 +1718,7 @@ impl TextArea {
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn insert_named_element(&mut self, text: &str, id: String) {
|
||||
self.invalidate_vim_repeat_change();
|
||||
let start = self.clamp_pos_for_insertion(self.cursor_pos);
|
||||
self.insert_str_at(start, text);
|
||||
let end = start + text.len();
|
||||
@@ -1681,6 +1734,7 @@ impl TextArea {
|
||||
.iter()
|
||||
.position(|e| e.name.as_deref() == Some(id))
|
||||
{
|
||||
self.invalidate_vim_repeat_change();
|
||||
let range = self.elements[idx].range.clone();
|
||||
self.replace_range_raw(range, text);
|
||||
self.elements.retain(|e| e.name.as_deref() != Some(id));
|
||||
@@ -1699,6 +1753,7 @@ impl TextArea {
|
||||
.iter()
|
||||
.position(|e| e.name.as_deref() == Some(id))
|
||||
{
|
||||
self.invalidate_vim_repeat_change();
|
||||
let old_range = self.elements[elem_idx].range.clone();
|
||||
let start = old_range.start;
|
||||
self.replace_range_raw(old_range, text);
|
||||
@@ -1757,6 +1812,7 @@ impl TextArea {
|
||||
return None;
|
||||
}
|
||||
let id = self.add_element(start..end);
|
||||
self.invalidate_vim_repeat_change();
|
||||
Some(id)
|
||||
}
|
||||
|
||||
@@ -1769,7 +1825,11 @@ impl TextArea {
|
||||
let len_before = self.elements.len();
|
||||
self.elements
|
||||
.retain(|elem| elem.range.start != start || elem.range.end != end);
|
||||
len_before != self.elements.len()
|
||||
let removed = len_before != self.elements.len();
|
||||
if removed {
|
||||
self.invalidate_vim_repeat_change();
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
fn next_element_id(&mut self) -> u64 {
|
||||
@@ -2991,6 +3051,119 @@ mod tests {
|
||||
assert_eq!(t.vim_mode_label(), Some("Normal"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_dot_repeats_deletes_and_change_insert_edits() {
|
||||
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('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "three");
|
||||
|
||||
let mut t = ta_with("one two");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
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));
|
||||
t.input(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "bar ".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "bar bar");
|
||||
assert_eq!(t.vim_mode_label(), Some("Normal"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_dot_repeats_paste_and_visual_changes() {
|
||||
let mut t = ta_with("a");
|
||||
t.kill_buffer = "!".to_string();
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "a!!");
|
||||
|
||||
let mut t = ta_with("one two");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('e'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "x ".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "x x");
|
||||
|
||||
let mut t = ta_with("one\ntwo");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('V'), KeyModifiers::SHIFT));
|
||||
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "x\n".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "x\nx");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_dot_repeats_substitutions_and_find_deletes() {
|
||||
let mut t = ta_with("ab cd");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('X'), KeyModifiers::SHIFT));
|
||||
t.input(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "Xb ".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "Xb Xd");
|
||||
|
||||
let mut t = ta_with("one\ntwo");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT));
|
||||
t.input(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
t.set_cursor(/*pos*/ "x\n".len());
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "x\nx");
|
||||
|
||||
let mut t = ta_with("a,b,c");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('f'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char(','), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_atomic_element_updates_invalidate_dot_repeat() {
|
||||
let mut t = ta_with("abc");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
t.input(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
|
||||
assert_eq!(t.text(), "bc");
|
||||
|
||||
t.add_element_range(/*range*/ 0..1).expect("valid element");
|
||||
t.set_cursor(/*pos*/ 1);
|
||||
t.input(KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "bc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_find_till_motions_repeat_and_stay_on_current_line() {
|
||||
let mut t = ta_with("ab,cd,ef\naz");
|
||||
|
||||
@@ -8,6 +8,7 @@ mod count;
|
||||
mod find;
|
||||
mod prose;
|
||||
mod register;
|
||||
mod repeat;
|
||||
mod tag;
|
||||
mod visual;
|
||||
|
||||
@@ -85,6 +86,69 @@ pub(super) struct VimFind {
|
||||
pub(super) target: char,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum VimRepeatTarget {
|
||||
Characters(usize),
|
||||
Lines(usize),
|
||||
Motion {
|
||||
motion: VimMotion,
|
||||
count: usize,
|
||||
},
|
||||
TextObject {
|
||||
object: VimTextObject,
|
||||
scope: VimTextObjectScope,
|
||||
count: usize,
|
||||
},
|
||||
Find {
|
||||
find: VimFind,
|
||||
count: usize,
|
||||
},
|
||||
Visual {
|
||||
kind: VimVisualKind,
|
||||
atomic_units: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum VimInsertEdit {
|
||||
Insert(String),
|
||||
DeleteBackward,
|
||||
DeleteForward,
|
||||
DeleteBackwardWord,
|
||||
DeleteForwardWord,
|
||||
KillLineStart,
|
||||
KillWholeLine,
|
||||
KillLineEnd,
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
MoveUp,
|
||||
MoveDown,
|
||||
MoveWordLeft,
|
||||
MoveWordRight,
|
||||
MoveLineStart { move_up_at_bol: bool },
|
||||
MoveLineEnd { move_down_at_eol: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct VimInsertRecording {
|
||||
pub(super) target: VimRepeatTarget,
|
||||
pub(super) edits: Vec<VimInsertEdit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum VimRepeatChange {
|
||||
Delete(VimRepeatTarget),
|
||||
Change {
|
||||
target: VimRepeatTarget,
|
||||
edits: Vec<VimInsertEdit>,
|
||||
},
|
||||
Paste {
|
||||
text: String,
|
||||
kind: super::KillBufferKind,
|
||||
count: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum VimTextObjectScope {
|
||||
Inner,
|
||||
|
||||
@@ -133,7 +133,7 @@ impl TextArea {
|
||||
self.vim_mode = super::VimMode::Insert;
|
||||
}
|
||||
|
||||
fn current_line_range_with_count(&self, count: usize) -> Range<usize> {
|
||||
pub(in super::super) 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;
|
||||
for _ in 1..count {
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::super::TextArea;
|
||||
use super::VimFind;
|
||||
use super::VimFindKind;
|
||||
use super::VimOperator;
|
||||
use super::VimRepeatTarget;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -102,7 +103,11 @@ impl TextArea {
|
||||
fn execute_vim_find(&mut self, operator: Option<VimOperator>, find: VimFind, count: usize) {
|
||||
if let Some(operator) = operator {
|
||||
if let Some(range) = self.range_for_find(find, count) {
|
||||
self.apply_vim_operator_to_range(operator, range);
|
||||
self.apply_vim_operator_to_range(
|
||||
operator,
|
||||
range,
|
||||
Some(VimRepeatTarget::Find { find, count }),
|
||||
);
|
||||
}
|
||||
} else if let Some(target) = self.target_for_find(find, count) {
|
||||
self.set_cursor(target);
|
||||
|
||||
@@ -66,8 +66,10 @@ impl TextArea {
|
||||
if source.0.is_empty() {
|
||||
return;
|
||||
}
|
||||
let previous_len = self.text.len();
|
||||
for _ in 0..count {
|
||||
self.paste_text_after_cursor(&source.0, source.1);
|
||||
}
|
||||
self.record_vim_paste_if_changed(source.0, source.1, count, previous_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
use super::super::KillBufferKind;
|
||||
use super::super::TextArea;
|
||||
use super::VimInsertEdit;
|
||||
use super::VimInsertRecording;
|
||||
use super::VimMode;
|
||||
use super::VimOperator;
|
||||
use super::VimRepeatChange;
|
||||
use super::VimRepeatTarget;
|
||||
use super::VimVisualKind;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::key_hint::is_altgr;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use std::ops::Range;
|
||||
|
||||
impl TextArea {
|
||||
pub(in super::super) fn invalidate_vim_repeat_change(&mut self) {
|
||||
if !self.vim_replaying_change {
|
||||
self.vim_last_change = None;
|
||||
self.vim_insert_recording = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn record_vim_delete_if_changed(
|
||||
&mut self,
|
||||
target: VimRepeatTarget,
|
||||
previous_len: usize,
|
||||
) {
|
||||
if !self.vim_replaying_change && self.text.len() < previous_len {
|
||||
self.vim_last_change = Some(VimRepeatChange::Delete(target));
|
||||
self.vim_insert_recording = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn begin_vim_change_recording(&mut self, target: VimRepeatTarget) {
|
||||
if !self.vim_replaying_change {
|
||||
self.vim_insert_recording = Some(VimInsertRecording {
|
||||
target,
|
||||
edits: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn complete_vim_change_recording(&mut self) {
|
||||
if self.vim_replaying_change {
|
||||
return;
|
||||
}
|
||||
if let Some(recording) = self.vim_insert_recording.take() {
|
||||
self.vim_last_change = Some(VimRepeatChange::Change {
|
||||
target: recording.target,
|
||||
edits: recording.edits,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn record_vim_paste_if_changed(
|
||||
&mut self,
|
||||
text: String,
|
||||
kind: KillBufferKind,
|
||||
count: usize,
|
||||
previous_len: usize,
|
||||
) {
|
||||
if !self.vim_replaying_change && self.text.len() > previous_len {
|
||||
self.vim_last_change = Some(VimRepeatChange::Paste { text, kind, count });
|
||||
self.vim_insert_recording = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn record_vim_insert_edit(&mut self, edit: Option<VimInsertEdit>) {
|
||||
if self.vim_replaying_change {
|
||||
return;
|
||||
}
|
||||
if let Some(recording) = self.vim_insert_recording.as_mut()
|
||||
&& let Some(edit) = edit
|
||||
{
|
||||
recording.edits.push(edit);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn vim_insert_edit_for_event(
|
||||
&self,
|
||||
event: KeyEvent,
|
||||
) -> Option<VimInsertEdit> {
|
||||
let keymap = &self.editor_keymap;
|
||||
if keymap.insert_newline.is_pressed(event) {
|
||||
return Some(VimInsertEdit::Insert("\n".to_string()));
|
||||
}
|
||||
if keymap.delete_backward_word.is_pressed(event) {
|
||||
return Some(VimInsertEdit::DeleteBackwardWord);
|
||||
}
|
||||
if let KeyEvent {
|
||||
code: KeyCode::Char(c),
|
||||
modifiers,
|
||||
..
|
||||
} = event
|
||||
&& is_altgr(modifiers)
|
||||
{
|
||||
return Some(VimInsertEdit::Insert(c.to_string()));
|
||||
}
|
||||
if keymap.delete_backward.is_pressed(event) {
|
||||
return Some(VimInsertEdit::DeleteBackward);
|
||||
}
|
||||
if keymap.delete_forward_word.is_pressed(event) {
|
||||
return Some(VimInsertEdit::DeleteForwardWord);
|
||||
}
|
||||
if keymap.delete_forward.is_pressed(event) {
|
||||
return Some(VimInsertEdit::DeleteForward);
|
||||
}
|
||||
if keymap.kill_line_start.is_pressed(event) {
|
||||
return Some(VimInsertEdit::KillLineStart);
|
||||
}
|
||||
if keymap.kill_whole_line.is_pressed(event) {
|
||||
return Some(VimInsertEdit::KillWholeLine);
|
||||
}
|
||||
if keymap.kill_line_end.is_pressed(event) {
|
||||
return Some(VimInsertEdit::KillLineEnd);
|
||||
}
|
||||
if keymap.yank.is_pressed(event) {
|
||||
return (!self.kill_buffer.is_empty())
|
||||
.then(|| VimInsertEdit::Insert(self.kill_buffer.clone()));
|
||||
}
|
||||
if keymap.move_word_left.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveWordLeft);
|
||||
}
|
||||
if keymap.move_word_right.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveWordRight);
|
||||
}
|
||||
if keymap.move_left.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveLeft);
|
||||
}
|
||||
if keymap.move_right.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveRight);
|
||||
}
|
||||
if keymap.move_up.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveUp);
|
||||
}
|
||||
if keymap.move_down.is_pressed(event) {
|
||||
return Some(VimInsertEdit::MoveDown);
|
||||
}
|
||||
if keymap.move_line_start.is_pressed(event) {
|
||||
let move_up_at_bol = matches!(
|
||||
event,
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}
|
||||
);
|
||||
return Some(VimInsertEdit::MoveLineStart { move_up_at_bol });
|
||||
}
|
||||
if keymap.move_line_end.is_pressed(event) {
|
||||
let move_down_at_eol = matches!(
|
||||
event,
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('e'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}
|
||||
);
|
||||
return Some(VimInsertEdit::MoveLineEnd { move_down_at_eol });
|
||||
}
|
||||
if let KeyEvent {
|
||||
code: KeyCode::Char(c),
|
||||
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
|
||||
..
|
||||
} = event
|
||||
&& !c.is_ascii_control()
|
||||
{
|
||||
return Some(VimInsertEdit::Insert(c.to_string()));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(in super::super) fn repeat_vim_change(&mut self) {
|
||||
let Some(change) = self.vim_last_change.clone() else {
|
||||
return;
|
||||
};
|
||||
self.vim_replaying_change = true;
|
||||
match change {
|
||||
VimRepeatChange::Delete(target) => {
|
||||
self.execute_vim_repeat_target(VimOperator::Delete, target);
|
||||
}
|
||||
VimRepeatChange::Change { target, edits } => {
|
||||
if self.execute_vim_repeat_target(VimOperator::Change, target) {
|
||||
for edit in edits {
|
||||
self.apply_vim_insert_edit(edit);
|
||||
}
|
||||
self.exit_vim_insert_after_change();
|
||||
}
|
||||
}
|
||||
VimRepeatChange::Paste { text, kind, count } => {
|
||||
for _ in 0..count {
|
||||
self.paste_text_after_cursor(&text, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.vim_replaying_change = false;
|
||||
self.clear_vim_counts();
|
||||
self.clear_vim_register_selection();
|
||||
}
|
||||
|
||||
fn execute_vim_repeat_target(
|
||||
&mut self,
|
||||
operator: VimOperator,
|
||||
target: VimRepeatTarget,
|
||||
) -> bool {
|
||||
let previous_len = self.text.len();
|
||||
match target {
|
||||
VimRepeatTarget::Characters(count) => {
|
||||
self.delete_forward_kill(count);
|
||||
if operator == VimOperator::Change {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
}
|
||||
}
|
||||
VimRepeatTarget::Lines(count) => match operator {
|
||||
VimOperator::Delete => self.vim_kill_current_lines(count),
|
||||
VimOperator::Change => self.vim_change_current_lines(count),
|
||||
VimOperator::Yank => unreachable!("repeat changes never yank"),
|
||||
},
|
||||
VimRepeatTarget::Motion { motion, count } => {
|
||||
self.apply_vim_operator(operator, motion, count);
|
||||
}
|
||||
VimRepeatTarget::TextObject {
|
||||
object,
|
||||
scope,
|
||||
count,
|
||||
} => {
|
||||
if let Some(range) = self.counted_text_object_range(object, scope, count) {
|
||||
self.apply_vim_operator_to_range(operator, range, /*repeat_target*/ None);
|
||||
}
|
||||
}
|
||||
VimRepeatTarget::Find { find, count } => {
|
||||
if let Some(range) = self.range_for_find(find, count) {
|
||||
self.apply_vim_operator_to_range(operator, range, /*repeat_target*/ None);
|
||||
}
|
||||
}
|
||||
VimRepeatTarget::Visual { kind, atomic_units } => {
|
||||
if let Some(mut range) = self.visual_repeat_range(kind, atomic_units) {
|
||||
if operator == VimOperator::Change
|
||||
&& kind == VimVisualKind::Linewise
|
||||
&& range.end > range.start
|
||||
&& self.text[..range.end].ends_with('\n')
|
||||
{
|
||||
range.end -= 1;
|
||||
}
|
||||
let buffer_kind = if kind == VimVisualKind::Linewise {
|
||||
KillBufferKind::Linewise
|
||||
} else {
|
||||
KillBufferKind::Characterwise
|
||||
};
|
||||
self.kill_range_with_kind(range, buffer_kind);
|
||||
if operator == VimOperator::Change {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.text.len() < previous_len
|
||||
|| operator == VimOperator::Change && self.vim_mode == VimMode::Insert
|
||||
}
|
||||
|
||||
pub(in super::super) fn visual_repeat_target(
|
||||
&self,
|
||||
kind: VimVisualKind,
|
||||
range: Range<usize>,
|
||||
) -> VimRepeatTarget {
|
||||
let atomic_units = match kind {
|
||||
VimVisualKind::Characterwise => {
|
||||
let mut pos = range.start;
|
||||
let mut units = 0;
|
||||
while pos < range.end {
|
||||
let next = self.next_atomic_boundary(pos);
|
||||
if next <= pos {
|
||||
break;
|
||||
}
|
||||
units += 1;
|
||||
pos = next;
|
||||
}
|
||||
units.max(1)
|
||||
}
|
||||
VimVisualKind::Linewise => self.text[range].lines().count().max(1),
|
||||
};
|
||||
VimRepeatTarget::Visual { kind, atomic_units }
|
||||
}
|
||||
|
||||
fn visual_repeat_range(
|
||||
&self,
|
||||
kind: VimVisualKind,
|
||||
atomic_units: usize,
|
||||
) -> Option<Range<usize>> {
|
||||
let range = match kind {
|
||||
VimVisualKind::Characterwise => {
|
||||
let mut end = self.cursor_pos;
|
||||
for _ in 0..atomic_units {
|
||||
end = self.next_atomic_boundary(end);
|
||||
}
|
||||
self.cursor_pos..end
|
||||
}
|
||||
VimVisualKind::Linewise => self.current_line_range_with_count(atomic_units),
|
||||
};
|
||||
(range.start < range.end).then(|| self.expand_range_to_element_boundaries(range))
|
||||
}
|
||||
|
||||
fn apply_vim_insert_edit(&mut self, edit: VimInsertEdit) {
|
||||
match edit {
|
||||
VimInsertEdit::Insert(text) => self.insert_str(&text),
|
||||
VimInsertEdit::DeleteBackward => self.delete_backward(/*n*/ 1),
|
||||
VimInsertEdit::DeleteForward => self.delete_forward(/*n*/ 1),
|
||||
VimInsertEdit::DeleteBackwardWord => self.delete_backward_word(),
|
||||
VimInsertEdit::DeleteForwardWord => self.delete_forward_word(),
|
||||
VimInsertEdit::KillLineStart => self.kill_to_beginning_of_line(),
|
||||
VimInsertEdit::KillWholeLine => self.kill_current_line(),
|
||||
VimInsertEdit::KillLineEnd => self.kill_to_end_of_line(),
|
||||
VimInsertEdit::MoveLeft => self.move_cursor_left(),
|
||||
VimInsertEdit::MoveRight => self.move_cursor_right(),
|
||||
VimInsertEdit::MoveUp => self.move_cursor_up(),
|
||||
VimInsertEdit::MoveDown => self.move_cursor_down(),
|
||||
VimInsertEdit::MoveWordLeft => self.set_cursor(self.beginning_of_previous_word()),
|
||||
VimInsertEdit::MoveWordRight => self.set_cursor(self.end_of_next_word()),
|
||||
VimInsertEdit::MoveLineStart { move_up_at_bol } => {
|
||||
self.move_cursor_to_beginning_of_line(move_up_at_bol);
|
||||
}
|
||||
VimInsertEdit::MoveLineEnd { move_down_at_eol } => {
|
||||
self.move_cursor_to_end_of_line(move_down_at_eol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn exit_vim_insert_after_change(&mut self) {
|
||||
let bol = self.beginning_of_current_line();
|
||||
if self.cursor_pos > bol {
|
||||
self.cursor_pos = self.prev_atomic_boundary(self.cursor_pos).max(bol);
|
||||
}
|
||||
self.enter_vim_normal_mode();
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,15 @@ impl TextArea {
|
||||
} else {
|
||||
KillBufferKind::Characterwise
|
||||
};
|
||||
let repeat_target = self.visual_repeat_target(
|
||||
if linewise {
|
||||
VimVisualKind::Linewise
|
||||
} else {
|
||||
VimVisualKind::Characterwise
|
||||
},
|
||||
range.clone(),
|
||||
);
|
||||
let previous_len = self.text.len();
|
||||
let start = range.start;
|
||||
match operator {
|
||||
VimOperator::Delete => self.kill_range_with_kind(range, kind),
|
||||
@@ -236,9 +245,13 @@ impl TextArea {
|
||||
self.clear_vim_register_selection();
|
||||
if operator == VimOperator::Change {
|
||||
self.vim_mode = VimMode::Insert;
|
||||
self.begin_vim_change_recording(repeat_target);
|
||||
} else {
|
||||
self.vim_mode = VimMode::Normal;
|
||||
self.set_cursor(start.min(self.vim_normal_end_cursor()));
|
||||
if operator == VimOperator::Delete {
|
||||
self.record_vim_delete_if_changed(repeat_target, previous_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ pub(crate) struct VimNormalKeymap {
|
||||
pub(crate) till_backward: Vec<KeyBinding>,
|
||||
pub(crate) repeat_find: Vec<KeyBinding>,
|
||||
pub(crate) repeat_find_reverse: Vec<KeyBinding>,
|
||||
pub(crate) repeat_change: Vec<KeyBinding>,
|
||||
pub(crate) delete_char: Vec<KeyBinding>,
|
||||
pub(crate) delete_to_line_end: Vec<KeyBinding>,
|
||||
pub(crate) change_to_line_end: Vec<KeyBinding>,
|
||||
@@ -523,6 +524,7 @@ impl RuntimeKeymap {
|
||||
till_backward: resolve_local!(keymap, defaults, vim_normal, till_backward),
|
||||
repeat_find: resolve_local!(keymap, defaults, vim_normal, repeat_find),
|
||||
repeat_find_reverse: resolve_local!(keymap, defaults, vim_normal, repeat_find_reverse),
|
||||
repeat_change: resolve_local!(keymap, defaults, vim_normal, repeat_change),
|
||||
delete_char: resolve_local!(keymap, defaults, vim_normal, delete_char),
|
||||
delete_to_line_end: resolve_local!(keymap, defaults, vim_normal, delete_to_line_end),
|
||||
change_to_line_end: resolve_local!(keymap, defaults, vim_normal, change_to_line_end),
|
||||
@@ -634,6 +636,10 @@ impl RuntimeKeymap {
|
||||
keymap.vim_normal.repeat_find_reverse.as_ref(),
|
||||
vim_normal.repeat_find_reverse.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_normal.repeat_change.as_ref(),
|
||||
vim_normal.repeat_change.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_normal.delete_char.as_ref(),
|
||||
vim_normal.delete_char.as_slice(),
|
||||
@@ -721,6 +727,11 @@ impl RuntimeKeymap {
|
||||
.repeat_find_reverse
|
||||
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
|
||||
}
|
||||
if keymap.vim_normal.repeat_change.is_none() {
|
||||
vim_normal
|
||||
.repeat_change
|
||||
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
|
||||
}
|
||||
if keymap.vim_normal.select_register.is_none() {
|
||||
vim_normal
|
||||
.select_register
|
||||
@@ -1275,6 +1286,7 @@ impl RuntimeKeymap {
|
||||
],
|
||||
repeat_find: default_bindings![plain(KeyCode::Char(';'))],
|
||||
repeat_find_reverse: default_bindings![plain(KeyCode::Char(','))],
|
||||
repeat_change: default_bindings![plain(KeyCode::Char('.'))],
|
||||
delete_char: default_bindings![plain(KeyCode::Char('x'))],
|
||||
delete_to_line_end: default_bindings![
|
||||
shift(KeyCode::Char('d')),
|
||||
@@ -1741,6 +1753,7 @@ impl RuntimeKeymap {
|
||||
"repeat_find_reverse",
|
||||
self.vim_normal.repeat_find_reverse.as_slice(),
|
||||
),
|
||||
("repeat_change", self.vim_normal.repeat_change.as_slice()),
|
||||
("delete_char", self.vim_normal.delete_char.as_slice()),
|
||||
(
|
||||
"delete_to_line_end",
|
||||
@@ -2698,6 +2711,16 @@ mod tests {
|
||||
assert_eq!(runtime.vim_normal.enter_visual_line, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_legacy_vim_binding_prunes_repeat_change_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.repeat_change, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_legacy_vim_operator_bindings_prune_new_text_object_defaults() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
|
||||
@@ -139,6 +139,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("vim_normal", "Vim normal", "till_backward", "Move until after the previous target character on the current line."),
|
||||
action("vim_normal", "Vim normal", "repeat_find", "Repeat the latest find or till motion."),
|
||||
action("vim_normal", "Vim normal", "repeat_find_reverse", "Repeat the latest find or till motion in reverse."),
|
||||
action("vim_normal", "Vim normal", "repeat_change", "Repeat the most recent text-changing Vim command."),
|
||||
action("vim_normal", "Vim normal", "delete_char", "Delete the character under the cursor."),
|
||||
action("vim_normal", "Vim normal", "delete_to_line_end", "Delete from cursor to end of line."),
|
||||
action("vim_normal", "Vim normal", "change_to_line_end", "Change from cursor to end of line and enter insert mode."),
|
||||
@@ -305,6 +306,7 @@ pub(super) fn binding_slot<'a>(
|
||||
("vim_normal", "till_backward") => Some(&mut keymap.vim_normal.till_backward),
|
||||
("vim_normal", "repeat_find") => Some(&mut keymap.vim_normal.repeat_find),
|
||||
("vim_normal", "repeat_find_reverse") => Some(&mut keymap.vim_normal.repeat_find_reverse),
|
||||
("vim_normal", "repeat_change") => Some(&mut keymap.vim_normal.repeat_change),
|
||||
("vim_normal", "delete_char") => Some(&mut keymap.vim_normal.delete_char),
|
||||
("vim_normal", "delete_to_line_end") => Some(&mut keymap.vim_normal.delete_to_line_end),
|
||||
("vim_normal", "change_to_line_end") => Some(&mut keymap.vim_normal.change_to_line_end),
|
||||
@@ -453,6 +455,7 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("vim_normal", "till_backward") => Some(runtime_keymap.vim_normal.till_backward.as_slice()),
|
||||
("vim_normal", "repeat_find") => Some(runtime_keymap.vim_normal.repeat_find.as_slice()),
|
||||
("vim_normal", "repeat_find_reverse") => Some(runtime_keymap.vim_normal.repeat_find_reverse.as_slice()),
|
||||
("vim_normal", "repeat_change") => Some(runtime_keymap.vim_normal.repeat_change.as_slice()),
|
||||
("vim_normal", "delete_char") => Some(runtime_keymap.vim_normal.delete_char.as_slice()),
|
||||
("vim_normal", "delete_to_line_end") => Some(runtime_keymap.vim_normal.delete_to_line_end.as_slice()),
|
||||
("vim_normal", "change_to_line_end") => Some(runtime_keymap.vim_normal.change_to_line_end.as_slice()),
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
131 actions, 1 customized, 2 unbound.
|
||||
132 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.
|
||||
132 actions, 0 customized, 3 unbound.
|
||||
133 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 (131 selectable)
|
||||
tab: All (132 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 (72 selectable)
|
||||
tab: Vim (73 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.
|
||||
131 actions, 0 customized, 2 unbound.
|
||||
132 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.
|
||||
131 actions, 0 customized, 2 unbound.
|
||||
132 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