From e8b8f90dacd02196e98103e65fcfb9ec647125e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hampus=20Fr=C3=B6jdholm?= Date: Mon, 25 Jul 2022 23:57:06 +0200 Subject: [PATCH 1/5] fix: Recalculate completion when going through prompt history --- helix-term/src/ui/prompt.rs | 4 ++++ helix-term/tests/integration.rs | 1 + helix-term/tests/test/prompt.rs | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 helix-term/tests/test/prompt.rs diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 6e7df907e977..7796a024e459 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -550,6 +550,8 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Backward); + self.recalculate_completion(cx.editor); + self.exit_selection(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } @@ -557,6 +559,8 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Forward); + self.recalculate_completion(cx.editor); + self.exit_selection(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 11bc4e4c7c40..8969e976e5f3 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -21,5 +21,6 @@ mod test { mod auto_pairs; mod commands; mod movement; + mod prompt; mod write; } diff --git a/helix-term/tests/test/prompt.rs b/helix-term/tests/test/prompt.rs new file mode 100644 index 000000000000..2ab9604c6dae --- /dev/null +++ b/helix-term/tests/test/prompt.rs @@ -0,0 +1,18 @@ +use super::*; + +use helix_term::application::Application; + +#[tokio::test] +async fn test_history_completion() -> anyhow::Result<()> { + test_key_sequence( + &mut Application::new(Args::default(), Config::default())?, + Some(":asdf:theme d"), + Some(&|app| { + assert!(!app.editor.is_err()); + }), + false, + ) + .await?; + + Ok(()) +} From 28975d50619e64de6b29adb7b7dbd8c1a6b2bef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hampus=20Fr=C3=B6jdholm?= Date: Thu, 28 Jul 2022 21:00:58 +0200 Subject: [PATCH 2/5] 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. --- helix-term/src/commands/dap.rs | 8 ++-- helix-term/src/ui/picker.rs | 4 +- helix-term/src/ui/prompt.rs | 73 +++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 9f6f4c15cd32..45a54bd0696c 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -580,7 +580,7 @@ pub fn dap_edit_condition(cx: &mut Context) { None => return, }; let callback = Box::pin(async move { - let call: Callback = Box::new(move |_editor, compositor| { + let call: Callback = Box::new(move |editor, compositor| { let mut prompt = Prompt::new( "condition:".into(), None, @@ -605,7 +605,7 @@ pub fn dap_edit_condition(cx: &mut Context) { }, ); if let Some(condition) = breakpoint.condition { - prompt.insert_str(&condition) + prompt.insert_str(&condition, editor) } compositor.push(Box::new(prompt)); }); @@ -622,7 +622,7 @@ pub fn dap_edit_log(cx: &mut Context) { None => return, }; let callback = Box::pin(async move { - let call: Callback = Box::new(move |_editor, compositor| { + let call: Callback = Box::new(move |editor, compositor| { let mut prompt = Prompt::new( "log-message:".into(), None, @@ -646,7 +646,7 @@ pub fn dap_edit_log(cx: &mut Context) { }, ); if let Some(log_message) = breakpoint.log_message { - prompt.insert_str(&log_message); + prompt.insert_str(&log_message, editor); } compositor.push(Box::new(prompt)); }); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9707c81ee2b2..4a035126f572 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -466,12 +466,12 @@ impl Picker { .map(|(index, _score)| &self.options[*index]) } - pub fn save_filter(&mut self, cx: &Context) { + pub fn save_filter(&mut self, cx: &mut Context) { self.filters.clear(); self.filters .extend(self.matches.iter().map(|(index, _)| *index)); self.filters.sort_unstable(); // used for binary search later - self.prompt.clear(cx); + self.prompt.clear(cx.editor); } pub fn toggle_preview(&mut self) { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 7796a024e459..2a2ba2ab65d0 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -213,13 +213,15 @@ impl Prompt { if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { self.cursor = pos; } - self.recalculate_completion(cx.editor); self.exit_selection(); + self.recalculate_completion(cx.editor); } - pub fn insert_str(&mut self, s: &str) { + pub fn insert_str(&mut self, s: &str, editor: &Editor) { self.line.insert_str(self.cursor, s); self.cursor += s.len(); + self.exit_selection(); + self.recalculate_completion(editor); } pub fn move_cursor(&mut self, movement: Movement) { @@ -235,65 +237,72 @@ impl Prompt { self.cursor = self.line.len(); } - pub fn delete_char_backwards(&mut self, cx: &Context) { + pub fn delete_char_backwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::BackwardChar(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_char_forwards(&mut self, cx: &Context) { + pub fn delete_char_forwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::ForwardChar(1)); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_word_backwards(&mut self, cx: &Context) { + pub fn delete_word_backwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::BackwardWord(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_word_forwards(&mut self, cx: &Context) { + pub fn delete_word_forwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::ForwardWord(1)); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn kill_to_start_of_line(&mut self, cx: &Context) { + pub fn kill_to_start_of_line(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::StartOfLine); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn kill_to_end_of_line(&mut self, cx: &Context) { + pub fn kill_to_end_of_line(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn clear(&mut self, cx: &Context) { + pub fn clear(&mut self, editor: &Editor) { self.line.clear(); self.cursor = 0; - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); self.exit_selection(); } - pub fn change_history(&mut self, register: &[String], direction: CompletionDirection) { + pub fn change_history( + &mut self, + cx: &mut Context, + register: char, + direction: CompletionDirection, + ) { + let register = cx.editor.registers.get_mut(register).read(); + if register.is_empty() { return; } @@ -313,6 +322,8 @@ impl Prompt { self.history_pos = Some(index); self.move_end(); + self.exit_selection(); + self.recalculate_completion(cx.editor); } pub fn change_completion_selection(&mut self, direction: CompletionDirection) { @@ -490,16 +501,18 @@ impl Component for Prompt { ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)), ctrl!('e') | key!(End) => self.move_end(), ctrl!('a') | key!(Home) => self.move_start(), - ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => self.delete_word_backwards(cx), - alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx), - ctrl!('k') => self.kill_to_end_of_line(cx), - ctrl!('u') => self.kill_to_start_of_line(cx), + ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => { + self.delete_word_backwards(cx.editor) + } + alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx.editor), + ctrl!('k') => self.kill_to_end_of_line(cx.editor), + ctrl!('u') => self.kill_to_start_of_line(cx.editor), ctrl!('h') | key!(Backspace) => { - self.delete_char_backwards(cx); + self.delete_char_backwards(cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('d') | key!(Delete) => { - self.delete_char_forwards(cx); + self.delete_char_forwards(cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('s') => { @@ -516,7 +529,7 @@ impl Component for Prompt { ); let line = text.slice(range.from()..range.to()).to_string(); if !line.is_empty() { - self.insert_str(line.as_str()); + self.insert_str(line.as_str(), cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } @@ -548,19 +561,13 @@ impl Component for Prompt { } ctrl!('p') | key!(Up) => { if let Some(register) = self.history_register { - let register = cx.editor.registers.get_mut(register); - self.change_history(register.read(), CompletionDirection::Backward); - self.recalculate_completion(cx.editor); - self.exit_selection(); + self.change_history(cx, register, CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } ctrl!('n') | key!(Down) => { if let Some(register) = self.history_register { - let register = cx.editor.registers.get_mut(register); - self.change_history(register.read(), CompletionDirection::Forward); - self.recalculate_completion(cx.editor); - self.exit_selection(); + self.change_history(cx, register, CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } @@ -601,8 +608,8 @@ impl Component for Prompt { .read(c) .and_then(|r| r.first()) .map_or("", |r| r.as_str()), + context.editor, ); - prompt.recalculate_completion(context.editor); })); (self.callback_fn)(cx, &self.line, PromptEvent::Update); return EventResult::Consumed(None); From 4fea0c2612be808e47f73cec4c65e2b8b9bf8bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hampus=20Fr=C3=B6jdholm?= Date: Thu, 28 Jul 2022 21:05:26 +0200 Subject: [PATCH 3/5] 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. --- helix-term/src/ui/mod.rs | 19 ++++++------------- helix-term/src/ui/prompt.rs | 4 +++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 257608f00dac..462b53728ac6 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -35,10 +35,10 @@ pub fn prompt( completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, ) { - show_prompt( - cx, - Prompt::new(prompt, history_register, completion_fn, callback_fn), - ); + let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn); + // Calculate the initial completion + prompt.recalculate_completion(cx.editor); + cx.push_layer(Box::new(prompt)); } pub fn prompt_with_input( @@ -49,15 +49,8 @@ pub fn prompt_with_input( completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, ) { - show_prompt( - cx, - Prompt::new(prompt, history_register, completion_fn, callback_fn).with_line(input), - ); -} - -fn show_prompt(cx: &mut crate::commands::Context, mut prompt: Prompt) { - // Calculate initial completion - prompt.recalculate_completion(cx.editor); + let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn) + .with_line(input, cx.editor); cx.push_layer(Box::new(prompt)); } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 2a2ba2ab65d0..206a8ce7b586 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -84,10 +84,12 @@ impl Prompt { } } - pub fn with_line(mut self, line: String) -> Self { + pub fn with_line(mut self, line: String, editor: &Editor) -> Self { let cursor = line.len(); self.line = line; self.cursor = cursor; + self.exit_selection(); + self.recalculate_completion(editor); self } From 68480e014cd3e08fdfdb772b7bd0567fada905e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hampus=20Fr=C3=B6jdholm?= Date: Sun, 31 Jul 2022 13:38:00 +0200 Subject: [PATCH 4/5] 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. --- helix-term/src/ui/prompt.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 206a8ce7b586..20a5347a792c 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -88,7 +88,6 @@ impl Prompt { let cursor = line.len(); self.line = line; self.cursor = cursor; - self.exit_selection(); self.recalculate_completion(editor); self } @@ -98,6 +97,7 @@ impl Prompt { } pub fn recalculate_completion(&mut self, editor: &Editor) { + self.exit_selection(); self.completion = (self.completion_fn)(editor, &self.line); } @@ -215,14 +215,12 @@ impl Prompt { if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { self.cursor = pos; } - self.exit_selection(); self.recalculate_completion(cx.editor); } pub fn insert_str(&mut self, s: &str, editor: &Editor) { self.line.insert_str(self.cursor, s); self.cursor += s.len(); - self.exit_selection(); self.recalculate_completion(editor); } @@ -244,7 +242,6 @@ impl Prompt { self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); self.recalculate_completion(editor); } @@ -252,7 +249,6 @@ impl Prompt { let pos = self.eval_movement(Movement::ForwardChar(1)); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); self.recalculate_completion(editor); } @@ -261,7 +257,6 @@ impl Prompt { self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); self.recalculate_completion(editor); } @@ -269,7 +264,6 @@ impl Prompt { let pos = self.eval_movement(Movement::ForwardWord(1)); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); self.recalculate_completion(editor); } @@ -278,7 +272,6 @@ impl Prompt { self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); self.recalculate_completion(editor); } @@ -286,7 +279,6 @@ impl Prompt { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); self.recalculate_completion(editor); } @@ -294,7 +286,6 @@ impl Prompt { self.line.clear(); self.cursor = 0; self.recalculate_completion(editor); - self.exit_selection(); } pub fn change_history( @@ -324,7 +315,6 @@ impl Prompt { self.history_pos = Some(index); self.move_end(); - self.exit_selection(); self.recalculate_completion(cx.editor); } @@ -538,7 +528,6 @@ impl Component for Prompt { key!(Enter) => { if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) { self.recalculate_completion(cx.editor); - self.exit_selection(); } else { // handle executing with last command in history if nothing entered let input: Cow = if self.line.is_empty() { @@ -578,7 +567,6 @@ impl Component for Prompt { // if single completion candidate is a directory list content in completion if self.completion.len() == 1 && self.line.ends_with(std::path::MAIN_SEPARATOR) { self.recalculate_completion(cx.editor); - self.exit_selection(); } (self.callback_fn)(cx, &self.line, PromptEvent::Update) } From 6730a53c077eb6ee3f60155d8133a54b85d685a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 1 Sep 2022 01:25:02 +0900 Subject: [PATCH 5/5] Remove &mut on save_filter --- helix-term/src/ui/picker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 4a035126f572..079b798ca907 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -466,7 +466,7 @@ impl Picker { .map(|(index, _score)| &self.options[*index]) } - pub fn save_filter(&mut self, cx: &mut Context) { + pub fn save_filter(&mut self, cx: &Context) { self.filters.clear(); self.filters .extend(self.matches.iter().map(|(index, _)| *index));