mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
feat(tui): add vim prose text objects
This commit is contained in:
@@ -329,6 +329,10 @@ pub struct TuiVimTextObjectKeymap {
|
||||
pub single_quote: Option<KeybindingsSpec>,
|
||||
/// Text object: backticks.
|
||||
pub backtick: Option<KeybindingsSpec>,
|
||||
/// Text object: sentence.
|
||||
pub sentence: Option<KeybindingsSpec>,
|
||||
/// Text object: paragraph.
|
||||
pub paragraph: Option<KeybindingsSpec>,
|
||||
/// Cancel the pending text-object command.
|
||||
pub cancel: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
@@ -2884,7 +2884,9 @@
|
||||
"brackets": null,
|
||||
"cancel": null,
|
||||
"double_quote": null,
|
||||
"paragraph": null,
|
||||
"parentheses": null,
|
||||
"sentence": null,
|
||||
"single_quote": null,
|
||||
"word": null
|
||||
}
|
||||
@@ -3594,7 +3596,9 @@
|
||||
"brackets": null,
|
||||
"cancel": null,
|
||||
"double_quote": null,
|
||||
"paragraph": null,
|
||||
"parentheses": null,
|
||||
"sentence": null,
|
||||
"single_quote": null,
|
||||
"word": null
|
||||
}
|
||||
@@ -4284,6 +4288,14 @@
|
||||
],
|
||||
"description": "Text object: double quotes."
|
||||
},
|
||||
"paragraph": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Text object: paragraph."
|
||||
},
|
||||
"parentheses": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -4292,6 +4304,14 @@
|
||||
],
|
||||
"description": "Text object: parentheses."
|
||||
},
|
||||
"sentence": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Text object: sentence."
|
||||
},
|
||||
"single_quote": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -2840,6 +2840,75 @@ mod tests {
|
||||
assert_eq!(t.kill_buffer, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_sentence_text_objects_cover_change_and_around_delete() {
|
||||
let mut t = ta_with("One. Hello (world)! Next?");
|
||||
t.set_cursor(/*pos*/ "One. Hello (".len());
|
||||
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('s'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "One. Next?");
|
||||
assert_eq!(t.kill_buffer, "Hello (world)!");
|
||||
assert_eq!(t.vim_mode_label(), Some("Insert"));
|
||||
|
||||
let mut t = ta_with("One. Is this ok?) Next.");
|
||||
t.set_cursor(/*pos*/ "One. Is ".len());
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "One. Next.");
|
||||
assert_eq!(t.kill_buffer, "Is this ok?) ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_paragraph_text_objects_cover_multiline_delete_and_change() {
|
||||
let mut t = ta_with("one\nline\n\nsecond\nline\n\nthird");
|
||||
t.set_cursor(/*pos*/ "one\n".len());
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "\n\nsecond\nline\n\nthird");
|
||||
assert_eq!(t.kill_buffer, "one\nline");
|
||||
|
||||
let mut t = ta_with("one\nline\n\nsecond\nline\n\nthird");
|
||||
t.set_cursor(/*pos*/ "one\nline\n\nsecond".len());
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
|
||||
t.input(KeyEvent::new(KeyCode::Char('c'), 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\nline\n\nthird");
|
||||
assert_eq!(t.kill_buffer, "second\nline\n\n");
|
||||
assert_eq!(t.vim_mode_label(), Some("Insert"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_sentence_text_objects_preserve_unicode_and_atomic_elements() {
|
||||
let mut t = ta_with("Hi 👍! Use @file now? Done.");
|
||||
let mention_start = "Hi 👍! Use ".len();
|
||||
t.add_element_range(mention_start..mention_start + "@file".len())
|
||||
.expect("valid element");
|
||||
t.set_cursor(/*pos*/ mention_start);
|
||||
t.set_vim_enabled(/*enabled*/ true);
|
||||
|
||||
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
|
||||
t.input(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(t.text(), "Hi 👍! Done.");
|
||||
assert_eq!(t.kill_buffer, "Use @file now? ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vim_text_object_cancellation_does_not_edit() {
|
||||
let mut t = ta_with("hello world");
|
||||
|
||||
@@ -5,6 +5,7 @@ use crossterm::event::KeyEvent;
|
||||
use std::ops::Range;
|
||||
|
||||
mod find;
|
||||
mod prose;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum VimMode {
|
||||
@@ -78,6 +79,8 @@ pub(super) enum VimTextObject {
|
||||
DoubleQuote,
|
||||
SingleQuote,
|
||||
Backtick,
|
||||
Sentence,
|
||||
Paragraph,
|
||||
}
|
||||
|
||||
impl TextArea {
|
||||
@@ -127,6 +130,12 @@ impl TextArea {
|
||||
if self.vim_text_object_keymap.backtick.is_pressed(event) {
|
||||
return Some(VimTextObject::Backtick);
|
||||
}
|
||||
if self.vim_text_object_keymap.sentence.is_pressed(event) {
|
||||
return Some(VimTextObject::Sentence);
|
||||
}
|
||||
if self.vim_text_object_keymap.paragraph.is_pressed(event) {
|
||||
return Some(VimTextObject::Paragraph);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -144,6 +153,8 @@ impl TextArea {
|
||||
VimTextObject::DoubleQuote => self.quoted_text_object_range(scope, '"'),
|
||||
VimTextObject::SingleQuote => self.quoted_text_object_range(scope, '\''),
|
||||
VimTextObject::Backtick => self.quoted_text_object_range(scope, '`'),
|
||||
VimTextObject::Sentence => self.sentence_text_object_range(scope),
|
||||
VimTextObject::Paragraph => self.paragraph_text_object_range(scope),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
use super::super::TextArea;
|
||||
use super::VimTextObjectScope;
|
||||
use std::ops::Range;
|
||||
|
||||
impl TextArea {
|
||||
pub(in super::super) fn sentence_text_object_range(
|
||||
&self,
|
||||
scope: VimTextObjectScope,
|
||||
) -> Option<Range<usize>> {
|
||||
let inner = self.prose_range_at_cursor(self.sentence_ranges())?;
|
||||
Some(self.expand_prose_range(scope, inner))
|
||||
}
|
||||
|
||||
pub(in super::super) fn paragraph_text_object_range(
|
||||
&self,
|
||||
scope: VimTextObjectScope,
|
||||
) -> Option<Range<usize>> {
|
||||
let inner = self.prose_range_at_cursor(self.paragraph_ranges())?;
|
||||
Some(self.expand_prose_range(scope, inner))
|
||||
}
|
||||
|
||||
fn sentence_ranges(&self) -> Vec<Range<usize>> {
|
||||
let mut ranges = Vec::new();
|
||||
let mut start = self.skip_prose_separator(/*pos*/ 0);
|
||||
let mut pos = start;
|
||||
while pos < self.text.len() {
|
||||
let Some(ch) = self.text[pos..].chars().next() else {
|
||||
break;
|
||||
};
|
||||
let next = pos + ch.len_utf8();
|
||||
if !self.is_inside_element(pos) && matches!(ch, '.' | '!' | '?') {
|
||||
let end = self.sentence_closing_punctuation_end(next);
|
||||
if end == self.text.len()
|
||||
|| self.text[end..]
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(char::is_whitespace)
|
||||
{
|
||||
ranges.push(start..end);
|
||||
start = self.skip_prose_separator(end);
|
||||
pos = start;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
if start < self.text.len() {
|
||||
ranges.push(start..self.text.len());
|
||||
}
|
||||
ranges
|
||||
}
|
||||
|
||||
fn sentence_closing_punctuation_end(&self, mut pos: usize) -> usize {
|
||||
while pos < self.text.len() && !self.is_inside_element(pos) {
|
||||
let Some(ch) = self.text[pos..].chars().next() else {
|
||||
break;
|
||||
};
|
||||
if !matches!(ch, ')' | ']' | '}' | '"' | '\'' | '`') {
|
||||
break;
|
||||
}
|
||||
pos += ch.len_utf8();
|
||||
}
|
||||
pos
|
||||
}
|
||||
|
||||
fn paragraph_ranges(&self) -> Vec<Range<usize>> {
|
||||
let mut ranges = Vec::new();
|
||||
let mut block_start = None;
|
||||
let mut block_end = 0;
|
||||
let mut line_start = 0;
|
||||
loop {
|
||||
let line_end = self.text[line_start..]
|
||||
.find('\n')
|
||||
.map_or(self.text.len(), |offset| line_start + offset);
|
||||
if self.line_has_prose_content(line_start..line_end) {
|
||||
block_start.get_or_insert(line_start);
|
||||
block_end = line_end;
|
||||
} else if let Some(start) = block_start.take() {
|
||||
ranges.push(start..block_end);
|
||||
}
|
||||
if line_end == self.text.len() {
|
||||
break;
|
||||
}
|
||||
line_start = line_end + '\n'.len_utf8();
|
||||
}
|
||||
if let Some(start) = block_start {
|
||||
ranges.push(start..block_end);
|
||||
}
|
||||
ranges
|
||||
}
|
||||
|
||||
fn line_has_prose_content(&self, range: Range<usize>) -> bool {
|
||||
self.text[range.clone()]
|
||||
.char_indices()
|
||||
.any(|(offset, ch)| !ch.is_whitespace() || self.is_inside_element(range.start + offset))
|
||||
}
|
||||
|
||||
fn prose_range_at_cursor(&self, ranges: Vec<Range<usize>>) -> Option<Range<usize>> {
|
||||
ranges
|
||||
.iter()
|
||||
.find(|range| range.start <= self.cursor_pos && self.cursor_pos <= range.end)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
ranges
|
||||
.iter()
|
||||
.find(|range| self.cursor_pos < range.start)
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| ranges.last().cloned())
|
||||
}
|
||||
|
||||
fn expand_prose_range(&self, scope: VimTextObjectScope, inner: Range<usize>) -> Range<usize> {
|
||||
match scope {
|
||||
VimTextObjectScope::Inner => inner,
|
||||
VimTextObjectScope::Around => {
|
||||
let following = self.following_whitespace_end(inner.end);
|
||||
if following > inner.end {
|
||||
inner.start..following
|
||||
} else {
|
||||
self.preceding_whitespace_start(inner.start)..inner.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_prose_separator(&self, mut pos: usize) -> usize {
|
||||
while pos < self.text.len() && !self.is_inside_element(pos) {
|
||||
let Some(ch) = self.text[pos..].chars().next() else {
|
||||
break;
|
||||
};
|
||||
if !ch.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
pos += ch.len_utf8();
|
||||
}
|
||||
pos
|
||||
}
|
||||
}
|
||||
@@ -216,6 +216,8 @@ pub(crate) struct VimTextObjectKeymap {
|
||||
pub(crate) double_quote: Vec<KeyBinding>,
|
||||
pub(crate) single_quote: Vec<KeyBinding>,
|
||||
pub(crate) backtick: Vec<KeyBinding>,
|
||||
pub(crate) sentence: Vec<KeyBinding>,
|
||||
pub(crate) paragraph: Vec<KeyBinding>,
|
||||
pub(crate) cancel: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
@@ -877,7 +879,7 @@ impl RuntimeKeymap {
|
||||
.retain(|binding| !configured_vim_operator_bindings_to_preserve.contains(binding));
|
||||
}
|
||||
|
||||
let vim_text_object = VimTextObjectKeymap {
|
||||
let mut vim_text_object = VimTextObjectKeymap {
|
||||
word: resolve_local!(keymap, defaults, vim_text_object, word),
|
||||
big_word: resolve_local!(keymap, defaults, vim_text_object, big_word),
|
||||
parentheses: resolve_local!(keymap, defaults, vim_text_object, parentheses),
|
||||
@@ -886,9 +888,61 @@ impl RuntimeKeymap {
|
||||
double_quote: resolve_local!(keymap, defaults, vim_text_object, double_quote),
|
||||
single_quote: resolve_local!(keymap, defaults, vim_text_object, single_quote),
|
||||
backtick: resolve_local!(keymap, defaults, vim_text_object, backtick),
|
||||
sentence: resolve_local!(keymap, defaults, vim_text_object, sentence),
|
||||
paragraph: resolve_local!(keymap, defaults, vim_text_object, paragraph),
|
||||
cancel: resolve_local!(keymap, defaults, vim_text_object, cancel),
|
||||
};
|
||||
|
||||
let configured_vim_text_object_bindings_to_preserve = configured_bindings_to_preserve([
|
||||
(
|
||||
keymap.vim_text_object.word.as_ref(),
|
||||
vim_text_object.word.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.big_word.as_ref(),
|
||||
vim_text_object.big_word.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.parentheses.as_ref(),
|
||||
vim_text_object.parentheses.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.brackets.as_ref(),
|
||||
vim_text_object.brackets.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.braces.as_ref(),
|
||||
vim_text_object.braces.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.double_quote.as_ref(),
|
||||
vim_text_object.double_quote.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.single_quote.as_ref(),
|
||||
vim_text_object.single_quote.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.backtick.as_ref(),
|
||||
vim_text_object.backtick.as_slice(),
|
||||
),
|
||||
(
|
||||
keymap.vim_text_object.cancel.as_ref(),
|
||||
vim_text_object.cancel.as_slice(),
|
||||
),
|
||||
]);
|
||||
|
||||
if keymap.vim_text_object.sentence.is_none() {
|
||||
vim_text_object.sentence.retain(|binding| {
|
||||
!configured_vim_text_object_bindings_to_preserve.contains(binding)
|
||||
});
|
||||
}
|
||||
if keymap.vim_text_object.paragraph.is_none() {
|
||||
vim_text_object.paragraph.retain(|binding| {
|
||||
!configured_vim_text_object_bindings_to_preserve.contains(binding)
|
||||
});
|
||||
}
|
||||
|
||||
let pager = PagerKeymap {
|
||||
scroll_up: resolve_local!(keymap, defaults, pager, scroll_up),
|
||||
scroll_down: resolve_local!(keymap, defaults, pager, scroll_down),
|
||||
@@ -1245,6 +1299,8 @@ impl RuntimeKeymap {
|
||||
],
|
||||
single_quote: default_bindings![plain(KeyCode::Char('\''))],
|
||||
backtick: default_bindings![plain(KeyCode::Char('`'))],
|
||||
sentence: default_bindings![plain(KeyCode::Char('s'))],
|
||||
paragraph: default_bindings![plain(KeyCode::Char('p'))],
|
||||
cancel: default_bindings![plain(KeyCode::Esc)],
|
||||
},
|
||||
pager: PagerKeymap {
|
||||
@@ -1718,6 +1774,8 @@ impl RuntimeKeymap {
|
||||
("double_quote", self.vim_text_object.double_quote.as_slice()),
|
||||
("single_quote", self.vim_text_object.single_quote.as_slice()),
|
||||
("backtick", self.vim_text_object.backtick.as_slice()),
|
||||
("sentence", self.vim_text_object.sentence.as_slice()),
|
||||
("paragraph", self.vim_text_object.paragraph.as_slice()),
|
||||
("cancel", self.vim_text_object.cancel.as_slice()),
|
||||
],
|
||||
)?;
|
||||
@@ -2548,6 +2606,18 @@ mod tests {
|
||||
assert_eq!(runtime.vim_operator.select_around_text_object, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configured_legacy_vim_text_objects_prune_new_prose_defaults() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
keymap.vim_text_object.word = Some(one("s"));
|
||||
keymap.vim_text_object.big_word = Some(one("p"));
|
||||
|
||||
let runtime = RuntimeKeymap::from_config(&keymap).expect("config should parse");
|
||||
|
||||
assert_eq!(runtime.vim_text_object.sentence, Vec::new());
|
||||
assert_eq!(runtime.vim_text_object.paragraph, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_new_vim_operator_binding_still_conflicts_with_legacy_binding() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
|
||||
@@ -179,6 +179,8 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("vim_text_object", "Vim text object", "double_quote", "Target enclosing double quotes."),
|
||||
action("vim_text_object", "Vim text object", "single_quote", "Target enclosing single quotes."),
|
||||
action("vim_text_object", "Vim text object", "backtick", "Target enclosing backticks."),
|
||||
action("vim_text_object", "Vim text object", "sentence", "Target the current sentence."),
|
||||
action("vim_text_object", "Vim text object", "paragraph", "Target the current paragraph."),
|
||||
action("vim_text_object", "Vim text object", "cancel", "Cancel the pending text object."),
|
||||
action("pager", "Pager", "scroll_up", "Scroll up by one row."),
|
||||
action("pager", "Pager", "scroll_down", "Scroll down by one row."),
|
||||
@@ -335,6 +337,8 @@ pub(super) fn binding_slot<'a>(
|
||||
("vim_text_object", "double_quote") => Some(&mut keymap.vim_text_object.double_quote),
|
||||
("vim_text_object", "single_quote") => Some(&mut keymap.vim_text_object.single_quote),
|
||||
("vim_text_object", "backtick") => Some(&mut keymap.vim_text_object.backtick),
|
||||
("vim_text_object", "sentence") => Some(&mut keymap.vim_text_object.sentence),
|
||||
("vim_text_object", "paragraph") => Some(&mut keymap.vim_text_object.paragraph),
|
||||
("vim_text_object", "cancel") => Some(&mut keymap.vim_text_object.cancel),
|
||||
("pager", "scroll_up") => Some(&mut keymap.pager.scroll_up),
|
||||
("pager", "scroll_down") => Some(&mut keymap.pager.scroll_down),
|
||||
@@ -473,6 +477,8 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("vim_text_object", "double_quote") => Some(runtime_keymap.vim_text_object.double_quote.as_slice()),
|
||||
("vim_text_object", "single_quote") => Some(runtime_keymap.vim_text_object.single_quote.as_slice()),
|
||||
("vim_text_object", "backtick") => Some(runtime_keymap.vim_text_object.backtick.as_slice()),
|
||||
("vim_text_object", "sentence") => Some(runtime_keymap.vim_text_object.sentence.as_slice()),
|
||||
("vim_text_object", "paragraph") => Some(runtime_keymap.vim_text_object.paragraph.as_slice()),
|
||||
("vim_text_object", "cancel") => Some(runtime_keymap.vim_text_object.cancel.as_slice()),
|
||||
("pager", "scroll_up") => Some(runtime_keymap.pager.scroll_up.as_slice()),
|
||||
("pager", "scroll_down") => Some(runtime_keymap.pager.scroll_down.as_slice()),
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
121 actions, 1 customized, 2 unbound.
|
||||
123 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.
|
||||
122 actions, 0 customized, 3 unbound.
|
||||
124 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 (121 selectable)
|
||||
tab: All (123 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 (62 selectable)
|
||||
tab: Vim (64 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.
|
||||
121 actions, 0 customized, 2 unbound.
|
||||
123 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.
|
||||
121 actions, 0 customized, 2 unbound.
|
||||
123 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