on master help sign in
fix: Recalculate completion when going through prompt history (#3193)
4c9f144Frojdholm
helix-term/src/commands/dap.rs+4−4
@@ -582,7 +582,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
582 None => return,
582 None => return,
583 };
583 };
584 let callback = Box::pin(async move {
584 let callback = Box::pin(async move {
585− let call: Callback = Box::new(move |_editor, compositor| {
585+ let call: Callback = Box::new(move |editor, compositor| {
586 let mut prompt = Prompt::new(
586 let mut prompt = Prompt::new(
587 "condition:".into(),
587 "condition:".into(),
588 None,
588 None,
@@ -607,7 +607,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
607 },
607 },
608 );
608 );
609 if let Some(condition) = breakpoint.condition {
609 if let Some(condition) = breakpoint.condition {
610− prompt.insert_str(&condition)
610+ prompt.insert_str(&condition, editor)
611 }
611 }
612 compositor.push(Box::new(prompt));
612 compositor.push(Box::new(prompt));
613 });
613 });
@@ -624,7 +624,7 @@ pub fn dap_edit_log(cx: &mut Context) {
624 None => return,
624 None => return,
625 };
625 };
626 let callback = Box::pin(async move {
626 let callback = Box::pin(async move {
627− let call: Callback = Box::new(move |_editor, compositor| {
627+ let call: Callback = Box::new(move |editor, compositor| {
628 let mut prompt = Prompt::new(
628 let mut prompt = Prompt::new(
629 "log-message:".into(),
629 "log-message:".into(),
630 None,
630 None,
@@ -648,7 +648,7 @@ pub fn dap_edit_log(cx: &mut Context) {
648 },
648 },
649 );
649 );
650 if let Some(log_message) = breakpoint.log_message {
650 if let Some(log_message) = breakpoint.log_message {
651− prompt.insert_str(&log_message);
651+ prompt.insert_str(&log_message, editor);
652 }
652 }
653 compositor.push(Box::new(prompt));
653 compositor.push(Box::new(prompt));
654 });
654 });
helix-term/src/ui/mod.rs+6−13
@@ -35,10 +35,10 @@ pub fn prompt(
35 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
35 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
36 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
36 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
37) {
37) {
38− show_prompt(
39− cx,
40− Prompt::new(prompt, history_register, completion_fn, callback_fn),
38+ let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
41− );
39+ // Calculate the initial completion
40+ prompt.recalculate_completion(cx.editor);
41+ cx.push_layer(Box::new(prompt));
42}
42}
43
43
44pub fn prompt_with_input(
44pub fn prompt_with_input(
@@ -49,15 +49,8 @@ pub fn prompt_with_input(
49 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
49 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
50 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
50 callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
51) {
51) {
52− show_prompt(
53− cx,
54− Prompt::new(prompt, history_register, completion_fn, callback_fn).with_line(input),
52+ let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn)
55− );
53+ .with_line(input, cx.editor);
56−}
57−
58−fn show_prompt(cx: &mut crate::commands::Context, mut prompt: Prompt) {
59− // Calculate initial completion
60− prompt.recalculate_completion(cx.editor);
61 cx.push_layer(Box::new(prompt));
54 cx.push_layer(Box::new(prompt));
62}
55}
63
56
helix-term/src/ui/picker.rs+1−1
@@ -470,7 +470,7 @@ impl<T: Item> Picker<T> {
470 self.filters
470 self.filters
471 .extend(self.matches.iter().map(|(index, _)| *index));
471 .extend(self.matches.iter().map(|(index, _)| *index));
472 self.filters.sort_unstable(); // used for binary search later
472 self.filters.sort_unstable(); // used for binary search later
473− self.prompt.clear(cx);
473+ self.prompt.clear(cx.editor);
474 }
474 }
475
475
476 pub fn toggle_preview(&mut self) {
476 pub fn toggle_preview(&mut self) {
helix-term/src/ui/prompt.rs+40−39
@@ -83,10 +83,11 @@ impl Prompt {
83 }
83 }
84 }
84 }
85
85
86− pub fn with_line(mut self, line: String) -> Self {
86+ pub fn with_line(mut self, line: String, editor: &Editor) -> Self {
87 let cursor = line.len();
87 let cursor = line.len();
88 self.line = line;
88 self.line = line;
89 self.cursor = cursor;
89 self.cursor = cursor;
90+ self.recalculate_completion(editor);
90 self
91 self
91 }
92 }
92
93
@@ -95,6 +96,7 @@ impl Prompt {
95 }
96 }
96
97
97 pub fn recalculate_completion(&mut self, editor: &Editor) {
98 pub fn recalculate_completion(&mut self, editor: &Editor) {
99+ self.exit_selection();
98 self.completion = (self.completion_fn)(editor, &self.line);
100 self.completion = (self.completion_fn)(editor, &self.line);
99 }
101 }
100
102
@@ -213,12 +215,12 @@ impl Prompt {
213 self.cursor = pos;
215 self.cursor = pos;
214 }
216 }
215 self.recalculate_completion(cx.editor);
217 self.recalculate_completion(cx.editor);
216− self.exit_selection();
217 }
218 }
218
219
219− pub fn insert_str(&mut self, s: &str) {
220+ pub fn insert_str(&mut self, s: &str, editor: &Editor) {
220 self.line.insert_str(self.cursor, s);
221 self.line.insert_str(self.cursor, s);
221 self.cursor += s.len();
222 self.cursor += s.len();
223+ self.recalculate_completion(editor);
222 }
224 }
223
225
224 pub fn move_cursor(&mut self, movement: Movement) {
226 pub fn move_cursor(&mut self, movement: Movement) {
@@ -234,65 +236,65 @@ impl Prompt {
234 self.cursor = self.line.len();
236 self.cursor = self.line.len();
235 }
237 }
236
238
237− pub fn delete_char_backwards(&mut self, cx: &Context) {
239+ pub fn delete_char_backwards(&mut self, editor: &Editor) {
238 let pos = self.eval_movement(Movement::BackwardChar(1));
240 let pos = self.eval_movement(Movement::BackwardChar(1));
239 self.line.replace_range(pos..self.cursor, "");
241 self.line.replace_range(pos..self.cursor, "");
240 self.cursor = pos;
242 self.cursor = pos;
241
243
242− self.exit_selection();
243− self.recalculate_completion(cx.editor);
244+ self.recalculate_completion(editor);
244 }
245 }
245
246
246− pub fn delete_char_forwards(&mut self, cx: &Context) {
247+ pub fn delete_char_forwards(&mut self, editor: &Editor) {
247 let pos = self.eval_movement(Movement::ForwardChar(1));
248 let pos = self.eval_movement(Movement::ForwardChar(1));
248 self.line.replace_range(self.cursor..pos, "");
249 self.line.replace_range(self.cursor..pos, "");
249
250
250− self.exit_selection();
251− self.recalculate_completion(cx.editor);
251+ self.recalculate_completion(editor);
252 }
252 }
253
253
254− pub fn delete_word_backwards(&mut self, cx: &Context) {
254+ pub fn delete_word_backwards(&mut self, editor: &Editor) {
255 let pos = self.eval_movement(Movement::BackwardWord(1));
255 let pos = self.eval_movement(Movement::BackwardWord(1));
256 self.line.replace_range(pos..self.cursor, "");
256 self.line.replace_range(pos..self.cursor, "");
257 self.cursor = pos;
257 self.cursor = pos;
258
258
259− self.exit_selection();
260− self.recalculate_completion(cx.editor);
259+ self.recalculate_completion(editor);
261 }
260 }
262
261
263− pub fn delete_word_forwards(&mut self, cx: &Context) {
262+ pub fn delete_word_forwards(&mut self, editor: &Editor) {
264 let pos = self.eval_movement(Movement::ForwardWord(1));
263 let pos = self.eval_movement(Movement::ForwardWord(1));
265 self.line.replace_range(self.cursor..pos, "");
264 self.line.replace_range(self.cursor..pos, "");
266
265
267− self.exit_selection();
268− self.recalculate_completion(cx.editor);
266+ self.recalculate_completion(editor);
269 }
267 }
270
268
271− pub fn kill_to_start_of_line(&mut self, cx: &Context) {
269+ pub fn kill_to_start_of_line(&mut self, editor: &Editor) {
272 let pos = self.eval_movement(Movement::StartOfLine);
270 let pos = self.eval_movement(Movement::StartOfLine);
273 self.line.replace_range(pos..self.cursor, "");
271 self.line.replace_range(pos..self.cursor, "");
274 self.cursor = pos;
272 self.cursor = pos;
275
273
276− self.exit_selection();
277− self.recalculate_completion(cx.editor);
274+ self.recalculate_completion(editor);
278 }
275 }
279
276
280− pub fn kill_to_end_of_line(&mut self, cx: &Context) {
277+ pub fn kill_to_end_of_line(&mut self, editor: &Editor) {
281 let pos = self.eval_movement(Movement::EndOfLine);
278 let pos = self.eval_movement(Movement::EndOfLine);
282 self.line.replace_range(self.cursor..pos, "");
279 self.line.replace_range(self.cursor..pos, "");
283
280
284− self.exit_selection();
285− self.recalculate_completion(cx.editor);
281+ self.recalculate_completion(editor);
286 }
282 }
287
283
288− pub fn clear(&mut self, cx: &Context) {
284+ pub fn clear(&mut self, editor: &Editor) {
289 self.line.clear();
285 self.line.clear();
290 self.cursor = 0;
286 self.cursor = 0;
291− self.recalculate_completion(cx.editor);
287+ self.recalculate_completion(editor);
292− self.exit_selection();
293 }
288 }
294
289
290+ pub fn change_history(
291+ &mut self,
292+ cx: &mut Context,
293+ register: char,
295− pub fn change_history(&mut self, register: &[String], direction: CompletionDirection) {
294+ direction: CompletionDirection,
295+ ) {
296+ let register = cx.editor.registers.get_mut(register).read();
297+
296 if register.is_empty() {
298 if register.is_empty() {
297 return;
299 return;
298 }
300 }
@@ -312,6 +314,7 @@ impl Prompt {
312 self.history_pos = Some(index);
314 self.history_pos = Some(index);
313
315
314 self.move_end();
316 self.move_end();
317+ self.recalculate_completion(cx.editor);
315 }
318 }
316
319
317 pub fn change_completion_selection(&mut self, direction: CompletionDirection) {
320 pub fn change_completion_selection(&mut self, direction: CompletionDirection) {
@@ -494,16 +497,18 @@ impl Component for Prompt {
494 ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)),
497 ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)),
495 ctrl!('e') | key!(End) => self.move_end(),
498 ctrl!('e') | key!(End) => self.move_end(),
496 ctrl!('a') | key!(Home) => self.move_start(),
499 ctrl!('a') | key!(Home) => self.move_start(),
497− ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => self.delete_word_backwards(cx),
500+ ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => {
501+ self.delete_word_backwards(cx.editor)
502+ }
498− alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx),
503+ alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx.editor),
499− ctrl!('k') => self.kill_to_end_of_line(cx),
504+ ctrl!('k') => self.kill_to_end_of_line(cx.editor),
500− ctrl!('u') => self.kill_to_start_of_line(cx),
505+ ctrl!('u') => self.kill_to_start_of_line(cx.editor),
501 ctrl!('h') | key!(Backspace) => {
506 ctrl!('h') | key!(Backspace) => {
502− self.delete_char_backwards(cx);
507+ self.delete_char_backwards(cx.editor);
503 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
508 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
504 }
509 }
505 ctrl!('d') | key!(Delete) => {
510 ctrl!('d') | key!(Delete) => {
506− self.delete_char_forwards(cx);
511+ self.delete_char_forwards(cx.editor);
507 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
512 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
508 }
513 }
509 ctrl!('s') => {
514 ctrl!('s') => {
@@ -520,14 +525,13 @@ impl Component for Prompt {
520 );
525 );
521 let line = text.slice(range.from()..range.to()).to_string();
526 let line = text.slice(range.from()..range.to()).to_string();
522 if !line.is_empty() {
527 if !line.is_empty() {
523− self.insert_str(line.as_str());
528+ self.insert_str(line.as_str(), cx.editor);
524 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
529 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
525 }
530 }
526 }
531 }
527 key!(Enter) => {
532 key!(Enter) => {
528 if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
533 if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
529 self.recalculate_completion(cx.editor);
534 self.recalculate_completion(cx.editor);
530− self.exit_selection();
531 } else {
535 } else {
532 // handle executing with last command in history if nothing entered
536 // handle executing with last command in history if nothing entered
533 let input: Cow<str> = if self.line.is_empty() {
537 let input: Cow<str> = if self.line.is_empty() {
@@ -553,15 +557,13 @@ impl Component for Prompt {
553 }
557 }
554 ctrl!('p') | key!(Up) => {
558 ctrl!('p') | key!(Up) => {
555 if let Some(register) = self.history_register {
559 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);
558 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
561 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
559 }
562 }
560 }
563 }
561 ctrl!('n') | key!(Down) => {
564 ctrl!('n') | key!(Down) => {
562 if let Some(register) = self.history_register {
565 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);
565 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
567 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
566 }
568 }
567 }
569 }
@@ -570,7 +572,6 @@ impl Component for Prompt {
570 // if single completion candidate is a directory list content in completion
572 // if single completion candidate is a directory list content in completion
571 if self.completion.len() == 1 && self.line.ends_with(std::path::MAIN_SEPARATOR) {
573 if self.completion.len() == 1 && self.line.ends_with(std::path::MAIN_SEPARATOR) {
572 self.recalculate_completion(cx.editor);
574 self.recalculate_completion(cx.editor);
573− self.exit_selection();
574 }
575 }
575 (self.callback_fn)(cx, &self.line, PromptEvent::Update)
576 (self.callback_fn)(cx, &self.line, PromptEvent::Update)
576 }
577 }
@@ -602,8 +603,8 @@ impl Component for Prompt {
602 .read(c)
603 .read(c)
603 .and_then(|r| r.first())
604 .and_then(|r| r.first())
604 .map_or("", |r| r.as_str()),
605 .map_or("", |r| r.as_str()),
606+ context.editor,
605 );
607 );
606− prompt.recalculate_completion(context.editor);
607 }));
608 }));
608 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
609 (self.callback_fn)(cx, &self.line, PromptEvent::Update);
609 return EventResult::Consumed(None);
610 return EventResult::Consumed(None);
helix-term/tests/integration.rs+1
@@ -21,5 +21,6 @@ mod test {
21 mod auto_pairs;
21 mod auto_pairs;
22 mod commands;
22 mod commands;
23 mod movement;
23 mod movement;
24+ mod prompt;
24 mod write;
25 mod write;
25}
26}
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+}