From 4affae00379931ae420571965d36277d4a83f3e5 Mon Sep 17 00:00:00 2001 From: Basile Henry Date: Thu, 4 Apr 2024 18:00:34 +0200 Subject: [PATCH] helix-term: Add "partial" history to the prompt This makes it possible to look through the command history based on a prefix, which can make it much faster to find what we want when we know what it starts with! To use it, simply type the prefix before looking through command history with Up/Down arrow keys. The non-prefixed history works just like before, although now the state resets when the prompt is modified. --- helix-term/src/ui/prompt.rs | 68 +++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index a6ee7f05d..e169f41bc 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -4,6 +4,7 @@ use arc_swap::ArcSwap; use helix_core::syntax; use helix_view::input::KeyEvent; use helix_view::keyboard::KeyCode; +use std::num::NonZeroUsize; use std::sync::Arc; use std::{borrow::Cow, ops::RangeFrom}; use tui::buffer::Buffer as Surface; @@ -31,6 +32,7 @@ pub struct Prompt { selection: Option, history_register: Option, history_pos: Option, + history_prefix: Option, completion_fn: CompletionFn, callback_fn: CallbackFn, pub doc_fn: DocFn, @@ -83,6 +85,7 @@ impl Prompt { selection: None, history_register, history_pos: None, + history_prefix: None, completion_fn: Box::new(completion_fn), callback_fn: Box::new(callback_fn), doc_fn: Box::new(|_| None), @@ -96,6 +99,7 @@ impl Prompt { self.line = line; self.cursor = cursor; self.recalculate_completion(editor); + self.reset_history(); self } @@ -232,12 +236,14 @@ impl Prompt { self.cursor = pos; } self.recalculate_completion(cx.editor); + self.reset_history(); } pub fn insert_str(&mut self, s: &str, editor: &Editor) { self.line.insert_str(self.cursor, s); self.cursor += s.len(); self.recalculate_completion(editor); + self.reset_history(); } pub fn move_cursor(&mut self, movement: Movement) { @@ -259,6 +265,7 @@ impl Prompt { self.cursor = pos; self.recalculate_completion(editor); + self.reset_history(); } pub fn delete_char_forwards(&mut self, editor: &Editor) { @@ -266,6 +273,7 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.recalculate_completion(editor); + self.reset_history(); } pub fn delete_word_backwards(&mut self, editor: &Editor) { @@ -274,6 +282,7 @@ impl Prompt { self.cursor = pos; self.recalculate_completion(editor); + self.reset_history(); } pub fn delete_word_forwards(&mut self, editor: &Editor) { @@ -281,6 +290,7 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.recalculate_completion(editor); + self.reset_history(); } pub fn kill_to_start_of_line(&mut self, editor: &Editor) { @@ -289,6 +299,7 @@ impl Prompt { self.cursor = pos; self.recalculate_completion(editor); + self.reset_history(); } pub fn kill_to_end_of_line(&mut self, editor: &Editor) { @@ -296,12 +307,19 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.recalculate_completion(editor); + self.reset_history(); } pub fn clear(&mut self, editor: &Editor) { self.line.clear(); self.cursor = 0; self.recalculate_completion(editor); + self.reset_history(); + } + + pub fn reset_history(&mut self) { + self.history_pos = None; + self.history_prefix = None; } pub fn change_history( @@ -312,11 +330,13 @@ impl Prompt { ) { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); let mut values = match cx.editor.registers.read(register, cx.editor) { - Some(values) if values.len() > 0 => values.rev(), + Some(values) if values.len() > 0 => values.rev().enumerate(), _ => return, }; - let end = values.len().saturating_sub(1); + if self.history_pos.is_none() { + self.history_prefix = NonZeroUsize::new(self.line.len()); + } let index = match direction { CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1), @@ -324,18 +344,43 @@ impl Prompt { .history_pos .unwrap_or_else(|| values.len()) .saturating_sub(1), - } - .min(end); + }; - self.line = values.nth(index).unwrap().to_string(); - // Appease the borrow checker. - drop(values); + let history_line = if let Some(prefix_len) = self.history_prefix { + let prefix = &self.line[..prefix_len.get()]; - self.history_pos = Some(index); + match direction { + CompletionDirection::Forward => { + if index > 0 { + // Same as skip but without taking ownership + let _ = values.nth(index - 1); + } + values.find(|prev| prev.1.starts_with(prefix)) + } + CompletionDirection::Backward => { + let r_index = values.len() - 1 - index; + if r_index > 0 { + // Same as skip but without taking ownership + let _ = values.nth_back(r_index - 1); + } + values.rfind(|prev| prev.1.starts_with(prefix)) + } + } + } else { + values.nth(index) + }; - self.move_end(); - (self.callback_fn)(cx, &self.line, PromptEvent::Update); - self.recalculate_completion(cx.editor); + if let Some((index, line)) = history_line { + self.line = line.to_string(); + // Appease the borrow checker. + drop(values); + + self.history_pos = Some(index); + + self.move_end(); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + self.recalculate_completion(cx.editor); + } } pub fn change_completion_selection(&mut self, direction: CompletionDirection) { @@ -357,6 +402,7 @@ impl Prompt { self.line.replace_range(range.clone(), item); self.move_end(); + self.reset_history(); } pub fn exit_selection(&mut self) {