Skip to content

Commit

Permalink
Automatically track pseudo-pending text (helix-editor#4077)
Browse files Browse the repository at this point in the history
This change automatically tracks pending text for for commands which use
on-next-key callbacks. For example, `t` will await the next key event
and "t" will be shown in the bottom right-hand corner to show that we're
in a pending state.

Previously, the text for these on-next-key commands needed to be
hard-coded into the command definition which had some drawbacks:

* It was easy to forget to write and clear the pending text.
* If a command was remapped in a custom config, the pending text would
  still show the old key.

With this change, pending text is automatically tracked based on the
key events that lead to the command being executed. This works even
when the command is remapped in config and when the on-next-key
callback is nested under some key sequence (for example `mi`).
  • Loading branch information
the-mikedavis authored and Shekhinah Memmel committed Dec 11, 2022
1 parent e1dbf03 commit a53305a
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 49 deletions.
78 changes: 33 additions & 45 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,27 +1094,18 @@ fn extend_next_long_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_long_word_end)
}

fn will_find_char<F>(
cx: &mut Context,
search_fn: F,
inclusive: bool,
extend: bool,
pseudo_pending: &str,
) where
fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
where
F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
{
// TODO: count is reset to 1 before next key so we move it into the closure here.
// Would be nice to carry over.
let count = cx.count();

cx.editor.pseudo_pending = Some(pseudo_pending.to_string());

// need to wait for next key
// TODO: should this be done by grapheme rather than char? For example,
// we can't properly handle the line-ending CRLF case here in terms of char.
cx.on_next_key(move |cx, event| {
cx.editor.pseudo_pending = None;

let ch = match event {
KeyEvent {
code: KeyCode::Enter,
Expand Down Expand Up @@ -1217,35 +1208,35 @@ fn find_prev_char_impl(
}

fn find_till_char(cx: &mut Context) {
will_find_char(cx, find_next_char_impl, false, false, "t")
will_find_char(cx, find_next_char_impl, false, false)
}

fn find_next_char(cx: &mut Context) {
will_find_char(cx, find_next_char_impl, true, false, "f")
will_find_char(cx, find_next_char_impl, true, false)
}

fn extend_till_char(cx: &mut Context) {
will_find_char(cx, find_next_char_impl, false, true, "t")
will_find_char(cx, find_next_char_impl, false, true)
}

fn extend_next_char(cx: &mut Context) {
will_find_char(cx, find_next_char_impl, true, true, "f")
will_find_char(cx, find_next_char_impl, true, true)
}

fn till_prev_char(cx: &mut Context) {
will_find_char(cx, find_prev_char_impl, false, false, "T")
will_find_char(cx, find_prev_char_impl, false, false)
}

fn find_prev_char(cx: &mut Context) {
will_find_char(cx, find_prev_char_impl, true, false, "F")
will_find_char(cx, find_prev_char_impl, true, false)
}

fn extend_till_prev_char(cx: &mut Context) {
will_find_char(cx, find_prev_char_impl, false, true, "T")
will_find_char(cx, find_prev_char_impl, false, true)
}

fn extend_prev_char(cx: &mut Context) {
will_find_char(cx, find_prev_char_impl, true, true, "F")
will_find_char(cx, find_prev_char_impl, true, true)
}

fn repeat_last_motion(cx: &mut Context) {
Expand Down Expand Up @@ -4432,7 +4423,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {

cx.on_next_key(move |cx, event| {
cx.editor.autoinfo = None;
cx.editor.pseudo_pending = None;
if let Some(ch) = event.char() {
let textobject = move |editor: &mut Editor| {
let (view, doc) = current!(editor);
Expand Down Expand Up @@ -4481,33 +4471,31 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
}
});

if let Some((title, abbrev)) = match objtype {
textobject::TextObject::Inside => Some(("Match inside", "mi")),
textobject::TextObject::Around => Some(("Match around", "ma")),
let title = match objtype {
textobject::TextObject::Inside => "Match inside",
textobject::TextObject::Around => "Match around",
_ => return,
} {
let help_text = [
("w", "Word"),
("W", "WORD"),
("p", "Paragraph"),
("c", "Class (tree-sitter)"),
("f", "Function (tree-sitter)"),
("a", "Argument/parameter (tree-sitter)"),
("o", "Comment (tree-sitter)"),
("t", "Test (tree-sitter)"),
("m", "Closest surrounding pair to cursor"),
(" ", "... or any character acting as a pair"),
];

cx.editor.autoinfo = Some(Info::new(
title,
help_text
.into_iter()
.map(|(col1, col2)| (col1.to_string(), col2.to_string()))
.collect(),
));
cx.editor.pseudo_pending = Some(abbrev.to_string());
};
let help_text = [
("w", "Word"),
("W", "WORD"),
("p", "Paragraph"),
("c", "Class (tree-sitter)"),
("f", "Function (tree-sitter)"),
("a", "Argument/parameter (tree-sitter)"),
("o", "Comment (tree-sitter)"),
("t", "Test (tree-sitter)"),
("m", "Closest surrounding pair to cursor"),
(" ", "... or any character acting as a pair"),
];

cx.editor.autoinfo = Some(Info::new(
title,
help_text
.into_iter()
.map(|(col1, col2)| (col1.to_string(), col2.to_string()))
.collect(),
));
}

fn surround_add(cx: &mut Context) {
Expand Down
12 changes: 10 additions & 2 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use super::statusline;
pub struct EditorView {
pub keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
pseudo_pending: Vec<KeyEvent>,
last_insert: (commands::MappableCommand, Vec<InsertEvent>),
pub(crate) completion: Option<Completion>,
spinners: ProgressSpinners,
Expand All @@ -57,6 +58,7 @@ impl EditorView {
Self {
keymaps,
on_next_key: None,
pseudo_pending: Vec::new(),
last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
completion: None,
spinners: ProgressSpinners::default(),
Expand Down Expand Up @@ -841,6 +843,7 @@ impl EditorView {
event: KeyEvent,
) -> Option<KeymapResult> {
let mut last_mode = mode;
self.pseudo_pending.extend(self.keymaps.pending());
let key_result = self.keymaps.get(mode, event);
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox());

Expand Down Expand Up @@ -1323,6 +1326,11 @@ impl Component for EditorView {
}

self.on_next_key = cx.on_next_key_callback.take();
match self.on_next_key {
Some(_) => self.pseudo_pending.push(key),
None => self.pseudo_pending.clear(),
}

// appease borrowck
let callback = cx.callback.take();

Expand Down Expand Up @@ -1433,8 +1441,8 @@ impl Component for EditorView {
for key in self.keymaps.pending() {
disp.push_str(&key.key_sequence_format());
}
if let Some(pseudo_pending) = &cx.editor.pseudo_pending {
disp.push_str(pseudo_pending.as_str())
for key in &self.pseudo_pending {
disp.push_str(&key.key_sequence_format());
}
let style = cx.editor.theme.get("ui.text");
let macro_width = if cx.editor.macro_recording.is_some() {
Expand Down
2 changes: 0 additions & 2 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,6 @@ pub struct Editor {

pub idle_timer: Pin<Box<Sleep>>,
pub last_motion: Option<Motion>,
pub pseudo_pending: Option<String>,

pub last_completion: Option<CompleteAction>,

Expand Down Expand Up @@ -824,7 +823,6 @@ impl Editor {
idle_timer: Box::pin(sleep(conf.idle_timeout)),
last_motion: None,
last_completion: None,
pseudo_pending: None,
config,
auto_pairs,
exit_code: 0,
Expand Down

0 comments on commit a53305a

Please sign in to comment.