Skip to content

Commit e73bf7e

Browse files
Frojdholmarchseer
authored andcommitted
fix: Recalculate completion when going through prompt history (helix-editor#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>
1 parent d86a5b8 commit e73bf7e

File tree

6 files changed

+70
-57
lines changed

6 files changed

+70
-57
lines changed

helix-term/src/commands/dap.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ pub fn prompt(
3939
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
4040
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
4141
) {
42-
show_prompt(
43-
cx,
44-
Prompt::new(prompt, history_register, completion_fn, callback_fn),
45-
);
42+
let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
43+
// Calculate the initial completion
44+
prompt.recalculate_completion(cx.editor);
45+
cx.push_layer(Box::new(prompt));
4646
}
4747

4848
pub fn prompt_with_input(
@@ -53,15 +53,8 @@ pub fn prompt_with_input(
5353
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
5454
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
5555
) {
56-
show_prompt(
57-
cx,
58-
Prompt::new(prompt, history_register, completion_fn, callback_fn).with_line(input),
59-
);
60-
}
61-
62-
fn show_prompt(cx: &mut crate::commands::Context, mut prompt: Prompt) {
63-
// Calculate initial completion
64-
prompt.recalculate_completion(cx.editor);
56+
let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn)
57+
.with_line(input, cx.editor);
6558
cx.push_layer(Box::new(prompt));
6659
}
6760

helix-term/src/ui/picker.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -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.rs

+18
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)