on master help sign in
fix: Recalculate completion when going through prompt history (#3193)

* fix: Recalculate completion when going through prompt history * Update completion when the prompt line is changed It should not be possible to update the line without also updating the completion since the completion holds an index into the line. * Fix Prompt::with_line recalculate completion with_line was the last function where recalculate completion had to be done manually. This function now also recalculates the completion so that it's impossible to forget. * Exit selection when recalculating completion Keeping the selection index when the completion has been recalculated doesn't make sense. This clears the selection automatically, removing most needs to manually clear it. * Remove &mut on save_filter Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>

commit4c9f144dace484ca99cc84053493ed7b5f6946ac authorFrojdholm <Frojdholm@users.noreply.github.com> dateWed 31 Aug 2022, 18:26 (4 years ago) parente3e71fa
6 files changed+70−57
helix-term/src/commands/dap.rs+4−4 helix-term/src/ui/mod.rs+6−13 helix-term/src/ui/picker.rs+1−1 helix-term/src/ui/prompt.rs+40−39 helix-term/tests/integration.rs+1 helix-term/tests/test/prompt.rs+18
helix-term/src/commands/dap.rs+4−4
@@ -582,7 +582,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
582582 None => return,
583583 };
584584 let callback = Box::pin(async move {
585− let call: Callback = Box::new(move |_editor, compositor| {
585+ let call: Callback = Box::new(move |editor, compositor| {
586586 let mut prompt = Prompt::new(
587587 "condition:".into(),
588588 None,
@@ -607,7 +607,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
607607 },
608608 );
609609 if let Some(condition) = breakpoint.condition {
610− prompt.insert_str(&condition)
610+ prompt.insert_str(&condition, editor)
611611 }
612612 compositor.push(Box::new(prompt));
613613 });
@@ -624,7 +624,7 @@ pub fn dap_edit_log(cx: &mut Context) {
624624 None => return,
625625 };
626626 let callback = Box::pin(async move {
627− let call: Callback = Box::new(move |_editor, compositor| {
627+ let call: Callback = Box::new(move |editor, compositor| {
628628 let mut prompt = Prompt::new(
629629 "log-message:".into(),
630630 None,
@@ -648,7 +648,7 @@ pub fn dap_edit_log(cx: &mut Context) {
648648 },
649649 );
650650 if let Some(log_message) = breakpoint.log_message {
651− prompt.insert_str(&log_message);
651+ prompt.insert_str(&log_message, editor);
652652 }
653653 compositor.push(Box::new(prompt));
654654 });
helix-term/src/ui/mod.rs+6−13
@@ -35,10 +35,10 @@ pub fn prompt(
3535 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
3636 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
3737) {
38− show_prompt(
39− cx,
40− Prompt::new(prompt, history_register, completion_fn, callback_fn),
41− );
38+ let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
39+ // Calculate the initial completion
40+ prompt.recalculate_completion(cx.editor);
41+ cx.push_layer(Box::new(prompt));
4242}
4343
4444pub fn prompt_with_input(
@@ -49,15 +49,8 @@ pub fn prompt_with_input(
4949 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
5050 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
5151) {
52− show_prompt(
53− cx,
54− Prompt::new(prompt, history_register, completion_fn, callback_fn).with_line(input),
55− );
56−}
57−
58−fn show_prompt(cx: &mut crate::commands::Context, mut prompt: Prompt) {
59− // Calculate initial completion
60− prompt.recalculate_completion(cx.editor);
52+ let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn)
53+ .with_line(input, cx.editor);
6154 cx.push_layer(Box::new(prompt));
6255}
6356
helix-term/src/ui/picker.rs+1−1
@@ -470,7 +470,7 @@ impl<T: Item> Picker<T> {
470470 self.filters
471471 .extend(self.matches.iter().map(|(index, _)| *index));
472472 self.filters.sort_unstable(); // used for binary search later
473− self.prompt.clear(cx);
473+ self.prompt.clear(cx.editor);
474474 }
475475
476476 pub fn toggle_preview(&mut self) {
helix-term/src/ui/prompt.rs+40−39
@@ -83,10 +83,11 @@ impl Prompt {
8383 }
8484 }
8585
86− pub fn with_line(mut self, line: String) -> Self {
86+ pub fn with_line(mut self, line: String, editor: &Editor) -> Self {
8787 let cursor = line.len();
8888 self.line = line;
8989 self.cursor = cursor;
90+ self.recalculate_completion(editor);
9091 self
9192 }
9293
@@ -95,6 +96,7 @@ impl Prompt {
9596 }
9697
9798 pub fn recalculate_completion(&mut self, editor: &Editor) {
99+ self.exit_selection();
98100 self.completion = (self.completion_fn)(editor, &self.line);
99101 }
100102
@@ -213,12 +215,12 @@ impl Prompt {
213215 self.cursor = pos;
214216 }
215217 self.recalculate_completion(cx.editor);
216− self.exit_selection();
217218 }
218219
219− pub fn insert_str(&mut self, s: &str) {
220+ pub fn insert_str(&mut self, s: &str, editor: &Editor) {
220221 self.line.insert_str(self.cursor, s);
221222 self.cursor += s.len();
223+ self.recalculate_completion(editor);
222224 }
223225
224226 pub fn move_cursor(&mut self, movement: Movement) {
@@ -234,65 +236,65 @@ impl Prompt {
234236 self.cursor = self.line.len();
235237 }
236238
237− pub fn delete_char_backwards(&mut self, cx: &Context) {
239+ pub fn delete_char_backwards(&mut self, editor: &Editor) {
238240 let pos = self.eval_movement(Movement::BackwardChar(1));
239241 self.line.replace_range(pos..self.cursor, "");
240242 self.cursor = pos;
241243
242− self.exit_selection();
243− self.recalculate_completion(cx.editor);
244+ self.recalculate_completion(editor);
244245 }
245246
246− pub fn delete_char_forwards(&mut self, cx: &Context) {
247+ pub fn delete_char_forwards(&mut self, editor: &Editor) {
247248 let pos = self.eval_movement(Movement::ForwardChar(1));
248249 self.line.replace_range(self.cursor..pos, "");
249250
250− self.exit_selection();
251− self.recalculate_completion(cx.editor);
251+ self.recalculate_completion(editor);
252252 }
253253
254− pub fn delete_word_backwards(&mut self, cx: &Context) {
254+ pub fn delete_word_backwards(&mut self, editor: &Editor) {
255255 let pos = self.eval_movement(Movement::BackwardWord(1));
256256 self.line.replace_range(pos..self.cursor, "");
257257 self.cursor = pos;
258258
259− self.exit_selection();
260− self.recalculate_completion(cx.editor);
259+ self.recalculate_completion(editor);
261260 }
262261
263− pub fn delete_word_forwards(&mut self, cx: &Context) {
262+ pub fn delete_word_forwards(&mut self, editor: &Editor) {
264263 let pos = self.eval_movement(Movement::ForwardWord(1));
265264 self.line.replace_range(self.cursor..pos, "");
266265
267− self.exit_selection();
268− self.recalculate_completion(cx.editor);
266+ self.recalculate_completion(editor);
269267 }
270268
271− pub fn kill_to_start_of_line(&mut self, cx: &Context) {
269+ pub fn kill_to_start_of_line(&mut self, editor: &Editor) {
272270 let pos = self.eval_movement(Movement::StartOfLine);
273271 self.line.replace_range(pos..self.cursor, "");
274272 self.cursor = pos;
275273
276− self.exit_selection();
277− self.recalculate_completion(cx.editor);
274+ self.recalculate_completion(editor);
278275 }
279276
280− pub fn kill_to_end_of_line(&mut self, cx: &Context) {
277+ pub fn kill_to_end_of_line(&mut self, editor: &Editor) {
281278 let pos = self.eval_movement(Movement::EndOfLine);
282279 self.line.replace_range(self.cursor..pos, "");
283280
284− self.exit_selection();
285− self.recalculate_completion(cx.editor);
281+ self.recalculate_completion(editor);
286282 }
287283
288− pub fn clear(&mut self, cx: &Context) {
284+ pub fn clear(&mut self, editor: &Editor) {
289285 self.line.clear();
290286 self.cursor = 0;
291− self.recalculate_completion(cx.editor);
292− self.exit_selection();
287+ self.recalculate_completion(editor);
293288 }
294289
295− pub fn change_history(&mut self, register: &[String], direction: CompletionDirection) {
290+ pub fn change_history(
291+ &mut self,
292+ cx: &mut Context,
293+ register: char,
294+ direction: CompletionDirection,
295+ ) {
296+ let register = cx.editor.registers.get_mut(register).read();
297+
296298 if register.is_empty() {
297299 return;
298300 }
@@ -312,6 +314,7 @@ impl Prompt {
312314 self.history_pos = Some(index);
313315
314316 self.move_end();
317+ self.recalculate_completion(cx.editor);
315318 }
316319
317320 pub fn change_completion_selection(&mut self, direction: CompletionDirection) {
@@ -494,16 +497,18 @@ impl Component for Prompt {
494497 ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)),
495498 ctrl!('e') | key!(End) => self.move_end(),
496499 ctrl!('a') | key!(Home) => self.move_start(),
497− ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => self.delete_word_backwards(cx),
498− alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx),
499− ctrl!('k') => self.kill_to_end_of_line(cx),
500− ctrl!('u') => self.kill_to_start_of_line(cx),
500+ ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => {
501+ self.delete_word_backwards(cx.editor)
502+ }
503+ alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx.editor),
504+ ctrl!('k') => self.kill_to_end_of_line(cx.editor),
505+ ctrl!('u') => self.kill_to_start_of_line(cx.editor),
501506 ctrl!('h') | key!(Backspace) => {
502− self.delete_char_backwards(cx);
507+ self.delete_char_backwards(cx.editor);
503508 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
504509 }
505510 ctrl!('d') | key!(Delete) => {
506− self.delete_char_forwards(cx);
511+ self.delete_char_forwards(cx.editor);
507512 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
508513 }
509514 ctrl!('s') => {
@@ -520,14 +525,13 @@ impl Component for Prompt {
520525 );
521526 let line = text.slice(range.from()..range.to()).to_string();
522527 if !line.is_empty() {
523− self.insert_str(line.as_str());
528+ self.insert_str(line.as_str(), cx.editor);
524529 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
525530 }
526531 }
527532 key!(Enter) => {
528533 if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
529534 self.recalculate_completion(cx.editor);
530− self.exit_selection();
531535 } else {
532536 // handle executing with last command in history if nothing entered
533537 let input: Cow<str> = if self.line.is_empty() {
@@ -553,15 +557,13 @@ impl Component for Prompt {
553557 }
554558 ctrl!('p') | key!(Up) => {
555559 if let Some(register) = self.history_register {
556− let register = cx.editor.registers.get_mut(register);
557− self.change_history(register.read(), CompletionDirection::Backward);
560+ self.change_history(cx, register, CompletionDirection::Backward);
558561 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
559562 }
560563 }
561564 ctrl!('n') | key!(Down) => {
562565 if let Some(register) = self.history_register {
563− let register = cx.editor.registers.get_mut(register);
564− self.change_history(register.read(), CompletionDirection::Forward);
566+ self.change_history(cx, register, CompletionDirection::Forward);
565567 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
566568 }
567569 }
@@ -570,7 +572,6 @@ impl Component for Prompt {
570572 // if single completion candidate is a directory list content in completion
571573 if self.completion.len() == 1 && self.line.ends_with(std::path::MAIN_SEPARATOR) {
572574 self.recalculate_completion(cx.editor);
573− self.exit_selection();
574575 }
575576 (self.callback_fn)(cx, &self.line, PromptEvent::Update)
576577 }
@@ -602,8 +603,8 @@ impl Component for Prompt {
602603 .read(c)
603604 .and_then(|r| r.first())
604605 .map_or("", |r| r.as_str()),
606+ context.editor,
605607 );
606− prompt.recalculate_completion(context.editor);
607608 }));
608609 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
609610 return EventResult::Consumed(None);
helix-term/tests/integration.rs+1
@@ -21,5 +21,6 @@ mod test {
2121 mod auto_pairs;
2222 mod commands;
2323 mod movement;
24+ mod prompt;
2425 mod write;
2526}
helix-term/tests/test/prompt.rsnew file+18
@@ -0,0 +1,18 @@
1+use super::*;
2+
3+use helix_term::application::Application;
4+
5+#[tokio::test]
6+async fn test_history_completion() -> anyhow::Result<()> {
7+ test_key_sequence(
8+ &mut Application::new(Args::default(), Config::default())?,
9+ Some(":asdf<ret>:theme d<C-n><tab>"),
10+ Some(&|app| {
11+ assert!(!app.editor.is_err());
12+ }),
13+ false,
14+ )
15+ .await?;
16+
17+ Ok(())
18+}