feat(tui): add vim named registers

This commit is contained in:
Felipe Coury
2026-05-25 18:42:20 -03:00
parent 43472a6f02
commit 4494bfe368
14 changed files with 248 additions and 24 deletions
+2
View File
@@ -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`).
+10
View File
@@ -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": [
{
+122 -12
View File
@@ -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);
}
}
}
+22
View File
@@ -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();
+3
View File
@@ -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()),
@@ -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
@@ -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,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)
@@ -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