From 616f5c92e0ea5fe6dff128b9ef3a7b0b8c4717e4 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 9 Nov 2021 17:06:40 -0800 Subject: [PATCH 1/5] helix-term/commands: display buffer id in picker --- helix-term/src/commands.rs | 4 ++-- helix-view/src/lib.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fa1fa4e41831..e2c4a9d9df91 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3385,7 +3385,7 @@ fn buffer_picker(cx: &mut Context) { .map(helix_core::path::get_relative_path); let path = match path.as_deref().and_then(Path::to_str) { Some(path) => path, - None => return Cow::Borrowed(SCRATCH_BUFFER_NAME), + None => SCRATCH_BUFFER_NAME, }; let mut flags = Vec::new(); @@ -3401,7 +3401,7 @@ fn buffer_picker(cx: &mut Context) { } else { format!(" ({})", flags.join("")) }; - Cow::Owned(format!("{}{}", path, flag)) + Cow::Owned(format!("{} {}{}", self.id, path, flag)) } } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index a56c914d4475..e0964e1ca2d3 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -26,6 +26,12 @@ impl Default for DocumentId { } } +impl std::fmt::Display for DocumentId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + slotmap::new_key_type! { pub struct ViewId; } From 1a119ace9565f12cfc38cbbbc95bc88aa2fe271c Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sat, 20 Nov 2021 13:03:39 -0800 Subject: [PATCH 2/5] helix-term: implement buffer completer In order to implement this completer, the completion function needs to be able to access the compositor's context (to allow it to get the list of buffers currently open in the context's editor). --- helix-term/src/commands.rs | 72 +++++++++++++++++++++++----------- helix-term/src/commands/dap.rs | 7 ++-- helix-term/src/ui/mod.rs | 47 +++++++++++++++++++--- helix-term/src/ui/picker.rs | 8 ++-- helix-term/src/ui/prompt.rs | 55 +++++++++++++------------- 5 files changed, 127 insertions(+), 62 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e2c4a9d9df91..bf87f446fcd7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1435,7 +1435,7 @@ fn select_regex(cx: &mut Context) { cx, "select:".into(), Some(reg), - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1458,7 +1458,7 @@ fn split_selection(cx: &mut Context) { cx, "split:".into(), Some(reg), - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1600,7 +1600,7 @@ fn searcher(cx: &mut Context, direction: Direction) { cx, "search:".into(), Some(reg), - move |input: &str| { + move |_ctx: &compositor::Context, input: &str| { completions .iter() .filter(|comp| comp.starts_with(input)) @@ -1701,7 +1701,7 @@ fn global_search(cx: &mut Context) { cx, "global-search:".into(), None, - move |input: &str| { + move |_ctx: &compositor::Context, input: &str| { completions .iter() .filter(|comp| comp.starts_with(input)) @@ -2079,26 +2079,54 @@ pub mod cmd { Ok(()) } + fn buffer_close_impl( + editor: &mut Editor, + args: &[Cow], + force: bool, + ) -> anyhow::Result<()> { + if args.is_empty() { + let doc_id = view!(editor).doc; + editor.close_document(doc_id, force)?; + return Ok(()); + } + + for arg in args { + let doc_id = editor.documents().find_map(|doc| { + let arg_path = Some(Path::new(arg.as_ref())); + if doc.path().map(|p| p.as_path()) == arg_path + || doc.relative_path().as_deref() == arg_path + { + Some(doc.id()) + } else { + None + } + }); + + match doc_id { + Some(doc_id) => editor.close_document(doc_id, force)?, + None => { + editor.set_error(format!("couldn't close buffer '{}': does not exist", arg)); + } + } + } + + Ok(()) + } + fn buffer_close( cx: &mut compositor::Context, - _args: &[Cow], + args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - let view = view!(cx.editor); - let doc_id = view.doc; - cx.editor.close_document(doc_id, false)?; - Ok(()) + buffer_close_impl(cx.editor, args, false) } fn force_buffer_close( cx: &mut compositor::Context, - _args: &[Cow], + args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - let view = view!(cx.editor); - let doc_id = view.doc; - cx.editor.close_document(doc_id, true)?; - Ok(()) + buffer_close_impl(cx.editor, args, true) } fn write_impl(cx: &mut compositor::Context, path: Option<&Cow>) -> anyhow::Result<()> { @@ -2927,14 +2955,14 @@ pub mod cmd { aliases: &["bc", "bclose"], doc: "Close the current buffer.", fun: buffer_close, - completer: None, // FIXME: buffer completer + completer: Some(completers::buffer), }, TypableCommand { name: "buffer-close!", aliases: &["bc!", "bclose!"], doc: "Close the current buffer forcefully (ignoring unsaved changes).", fun: force_buffer_close, - completer: None, // FIXME: buffer completer + completer: Some(completers::buffer), }, TypableCommand { name: "write", @@ -3262,7 +3290,7 @@ fn command_mode(cx: &mut Context) { let mut prompt = Prompt::new( ":".into(), Some(':'), - |input: &str| { + |ctx: &compositor::Context, input: &str| { static FUZZY_MATCHER: Lazy = Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default); @@ -3294,7 +3322,7 @@ fn command_mode(cx: &mut Context) { .. }) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) { - completer(part) + completer(ctx, part) .into_iter() .map(|(range, file)| { // offset ranges to input @@ -5358,7 +5386,7 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) { cx, if !remove { "keep:" } else { "remove:" }.into(), Some(reg), - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -6122,7 +6150,7 @@ fn shell_keep_pipe(cx: &mut Context) { let prompt = Prompt::new( "keep-pipe:".into(), Some('|'), - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { let shell = &cx.editor.config.shell; if event != PromptEvent::Validate { @@ -6218,7 +6246,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { let prompt = Prompt::new( prompt, Some('|'), - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { let shell = &cx.editor.config.shell; if event != PromptEvent::Validate { @@ -6314,7 +6342,7 @@ fn rename_symbol(cx: &mut Context) { let prompt = Prompt::new( "rename-to:".into(), None, - |_input: &str| Vec::new(), + |_ctx: &compositor::Context, _input: &str| Vec::new(), move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return; diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 9da2715f4c8e..925c65c1d965 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -361,8 +361,9 @@ fn debug_parameter_prompt( let completer = match field_type { "filename" => ui::completers::filename, "directory" => ui::completers::directory, - _ => |_input: &str| Vec::new(), + _ => ui::completers::none, }; + Prompt::new( format!("{}: ", name).into(), None, @@ -696,7 +697,7 @@ pub fn dap_edit_condition(cx: &mut Context) { let mut prompt = Prompt::new( "condition:".into(), None, - |_input: &str| Vec::new(), + ui::completers::none, move |cx, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return; @@ -740,7 +741,7 @@ pub fn dap_edit_log(cx: &mut Context) { let mut prompt = Prompt::new( "log-message:".into(), None, - |_input: &str| Vec::new(), + ui::completers::none, move |cx, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 7f6d9f7c5789..263342b76760 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -30,7 +30,7 @@ pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, history_register: Option, - completion_fn: impl FnMut(&str) -> Vec + 'static, + completion_fn: impl FnMut(&crate::compositor::Context, &str) -> Vec + 'static, fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static, ) -> Prompt { let (view, doc) = current!(cx.editor); @@ -168,18 +168,53 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi } pub mod completers { + use crate::compositor::Context; use crate::ui::prompt::Completion; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; + use helix_view::document::SCRATCH_BUFFER_NAME; use helix_view::editor::Config; use helix_view::theme; use once_cell::sync::Lazy; use std::borrow::Cow; use std::cmp::Reverse; - pub type Completer = fn(&str) -> Vec; + pub type Completer = fn(&Context, &str) -> Vec; - pub fn theme(input: &str) -> Vec { + pub fn none(_cx: &Context, _input: &str) -> Vec { + Vec::new() + } + + pub fn buffer(cx: &Context, input: &str) -> Vec { + let mut names: Vec<_> = cx + .editor + .documents + .iter() + .map(|(_id, doc)| { + let name = doc + .relative_path() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME)); + ((0..), Cow::from(name)) + }) + .collect(); + + let matcher = Matcher::default(); + + let mut matches: Vec<_> = names + .into_iter() + .filter_map(|(_range, name)| { + matcher.fuzzy_match(&name, input).map(|score| (name, score)) + }) + .collect(); + + matches.sort_unstable_by_key(|(_file, score)| Reverse(*score)); + names = matches.into_iter().map(|(name, _)| ((0..), name)).collect(); + + names + } + + pub fn theme(_cx: &Context, input: &str) -> Vec { let mut names = theme::Loader::read_names(&helix_core::runtime_dir().join("themes")); names.extend(theme::Loader::read_names( &helix_core::config_dir().join("themes"), @@ -207,7 +242,7 @@ pub mod completers { names } - pub fn setting(input: &str) -> Vec { + pub fn setting(_cx: &Context, input: &str) -> Vec { static KEYS: Lazy> = Lazy::new(|| { serde_json::to_value(Config::default()) .unwrap() @@ -232,7 +267,7 @@ pub mod completers { .collect() } - pub fn filename(input: &str) -> Vec { + pub fn filename(_cx: &Context, input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); @@ -244,7 +279,7 @@ pub mod completers { }) } - pub fn directory(input: &str) -> Vec { + pub fn directory(_cx: &Context, input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9cddbc607122..dcc6400266f9 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -302,7 +302,7 @@ impl Picker { let prompt = Prompt::new( "".into(), None, - |_pattern: &str| Vec::new(), + |_ctx: &Context, _pattern: &str| Vec::new(), |_editor: &mut Context, _pattern: &str, _event: PromptEvent| { // }, @@ -395,12 +395,12 @@ impl Picker { .map(|(index, _score)| &self.options[*index]) } - pub fn save_filter(&mut self) { + pub fn save_filter(&mut self, cx: &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(); + self.prompt.clear(cx); } } @@ -468,7 +468,7 @@ impl Component for Picker { return close_fn; } ctrl!(' ') => { - self.save_filter(); + self.save_filter(cx); } _ => { if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 4c4fef268389..ff6b8c76e6b6 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -24,7 +24,7 @@ pub struct Prompt { selection: Option, history_register: Option, history_pos: Option, - completion_fn: Box Vec>, + completion_fn: Box Vec>, callback_fn: Box, pub doc_fn: Box Option<&'static str>>, } @@ -59,14 +59,14 @@ impl Prompt { pub fn new( prompt: Cow<'static, str>, history_register: Option, - mut completion_fn: impl FnMut(&str) -> Vec + 'static, + completion_fn: impl FnMut(&Context, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, ) -> Self { Self { prompt, line: String::new(), cursor: 0, - completion: completion_fn(""), + completion: Vec::new(), selection: None, history_register, history_pos: None, @@ -177,13 +177,13 @@ impl Prompt { } } - pub fn insert_char(&mut self, c: char) { + pub fn insert_char(&mut self, c: char, cx: &Context) { self.line.insert(self.cursor, c); let mut cursor = GraphemeCursor::new(self.cursor, self.line.len(), false); if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { self.cursor = pos; } - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); self.exit_selection(); } @@ -205,61 +205,61 @@ impl Prompt { self.cursor = self.line.len(); } - pub fn delete_char_backwards(&mut self) { + pub fn delete_char_backwards(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::BackwardChar(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn delete_char_forwards(&mut self) { + pub fn delete_char_forwards(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::ForwardChar(1)); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn delete_word_backwards(&mut self) { + pub fn delete_word_backwards(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::BackwardWord(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn delete_word_forwards(&mut self) { + pub fn delete_word_forwards(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::ForwardWord(1)); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn kill_to_start_of_line(&mut self) { + pub fn kill_to_start_of_line(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::StartOfLine); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn kill_to_end_of_line(&mut self) { + pub fn kill_to_end_of_line(&mut self, cx: &Context) { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); } - pub fn clear(&mut self) { + pub fn clear(&mut self, cx: &Context) { self.line.clear(); self.cursor = 0; - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); self.exit_selection(); } @@ -442,16 +442,16 @@ 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') => self.delete_word_backwards(), - alt!('d') => self.delete_word_forwards(), - ctrl!('k') => self.kill_to_end_of_line(), - ctrl!('u') => self.kill_to_start_of_line(), + ctrl!('w') => self.delete_word_backwards(cx), + alt!('d') => self.delete_word_forwards(cx), + ctrl!('k') => self.kill_to_end_of_line(cx), + ctrl!('u') => self.kill_to_start_of_line(cx), ctrl!('h') | key!(Backspace) => { - self.delete_char_backwards(); + self.delete_char_backwards(cx); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('d') | key!(Delete) => { - self.delete_char_forwards(); + self.delete_char_forwards(cx); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('s') => { @@ -474,7 +474,7 @@ impl Component for Prompt { } key!(Enter) => { if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) { - self.completion = (self.completion_fn)(&self.line); + self.completion = (self.completion_fn)(cx, &self.line); self.exit_selection(); } else { (self.callback_fn)(cx, &self.line, PromptEvent::Validate); @@ -515,7 +515,7 @@ impl Component for Prompt { code: KeyCode::Char(c), modifiers, } if !modifiers.contains(KeyModifiers::CONTROL) => { - self.insert_char(c); + self.insert_char(c, cx); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } _ => (), @@ -525,6 +525,7 @@ impl Component for Prompt { } fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { + self.completion = (self.completion_fn)(cx, &self.line); self.render_prompt(area, surface, cx) } From cb2eb95c85e8444a0cd1f160cb140be57e7720ee Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sat, 20 Nov 2021 13:42:40 -0800 Subject: [PATCH 3/5] WIP: show all buffers that couldn't be closed --- helix-term/src/commands.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bf87f446fcd7..2c1535b69dcd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2090,6 +2090,7 @@ pub mod cmd { return Ok(()); } + let mut nonexistent_buffers = vec![]; for arg in args { let doc_id = editor.documents().find_map(|doc| { let arg_path = Some(Path::new(arg.as_ref())); @@ -2104,12 +2105,19 @@ pub mod cmd { match doc_id { Some(doc_id) => editor.close_document(doc_id, force)?, - None => { - editor.set_error(format!("couldn't close buffer '{}': does not exist", arg)); - } + None => nonexistent_buffers.push(arg), } } + let nonexistent_buffers: Vec<_> = nonexistent_buffers + .into_iter() + .map(|str| format!("'{}'", str)) + .collect(); + editor.set_error(format!( + "couldn't close buffer(s) {}: does not exist", + nonexistent_buffers.join(", ") + )); + Ok(()) } From cc1abb74c7bc995cdd6a012b607a90105900868c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 17 Feb 2022 13:55:46 +0900 Subject: [PATCH 4/5] Pass through Editor instead of Context --- helix-term/src/commands.rs | 20 ++++++++++---------- helix-term/src/ui/mod.rs | 24 +++++++++++------------- helix-term/src/ui/picker.rs | 4 ++-- helix-term/src/ui/prompt.rs | 27 +++++++++++++++------------ 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2c1535b69dcd..9cf5630da2a1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1435,7 +1435,7 @@ fn select_regex(cx: &mut Context) { cx, "select:".into(), Some(reg), - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1458,7 +1458,7 @@ fn split_selection(cx: &mut Context) { cx, "split:".into(), Some(reg), - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1600,7 +1600,7 @@ fn searcher(cx: &mut Context, direction: Direction) { cx, "search:".into(), Some(reg), - move |_ctx: &compositor::Context, input: &str| { + move |_editor: &Editor, input: &str| { completions .iter() .filter(|comp| comp.starts_with(input)) @@ -1701,7 +1701,7 @@ fn global_search(cx: &mut Context) { cx, "global-search:".into(), None, - move |_ctx: &compositor::Context, input: &str| { + move |_editor: &Editor, input: &str| { completions .iter() .filter(|comp| comp.starts_with(input)) @@ -3298,7 +3298,7 @@ fn command_mode(cx: &mut Context) { let mut prompt = Prompt::new( ":".into(), Some(':'), - |ctx: &compositor::Context, input: &str| { + |editor: &Editor, input: &str| { static FUZZY_MATCHER: Lazy = Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default); @@ -3330,7 +3330,7 @@ fn command_mode(cx: &mut Context) { .. }) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) { - completer(ctx, part) + completer(editor, part) .into_iter() .map(|(range, file)| { // offset ranges to input @@ -5394,7 +5394,7 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) { cx, if !remove { "keep:" } else { "remove:" }.into(), Some(reg), - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -6158,7 +6158,7 @@ fn shell_keep_pipe(cx: &mut Context) { let prompt = Prompt::new( "keep-pipe:".into(), Some('|'), - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { let shell = &cx.editor.config.shell; if event != PromptEvent::Validate { @@ -6254,7 +6254,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { let prompt = Prompt::new( prompt, Some('|'), - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { let shell = &cx.editor.config.shell; if event != PromptEvent::Validate { @@ -6350,7 +6350,7 @@ fn rename_symbol(cx: &mut Context) { let prompt = Prompt::new( "rename-to:".into(), None, - |_ctx: &compositor::Context, _input: &str| Vec::new(), + ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 263342b76760..e9ff9d1a3408 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -22,7 +22,7 @@ pub use text::Text; use helix_core::regex::Regex; use helix_core::regex::RegexBuilder; -use helix_view::{Document, View}; +use helix_view::{Document, Editor, View}; use std::path::PathBuf; @@ -30,7 +30,7 @@ pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, history_register: Option, - completion_fn: impl FnMut(&crate::compositor::Context, &str) -> Vec + 'static, + completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static, ) -> Prompt { let (view, doc) = current!(cx.editor); @@ -168,26 +168,24 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi } pub mod completers { - use crate::compositor::Context; use crate::ui::prompt::Completion; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; use helix_view::document::SCRATCH_BUFFER_NAME; - use helix_view::editor::Config; use helix_view::theme; + use helix_view::{editor::Config, Editor}; use once_cell::sync::Lazy; use std::borrow::Cow; use std::cmp::Reverse; - pub type Completer = fn(&Context, &str) -> Vec; + pub type Completer = fn(&Editor, &str) -> Vec; - pub fn none(_cx: &Context, _input: &str) -> Vec { + pub fn none(_editor: &Editor, _input: &str) -> Vec { Vec::new() } - pub fn buffer(cx: &Context, input: &str) -> Vec { - let mut names: Vec<_> = cx - .editor + pub fn buffer(editor: &Editor, input: &str) -> Vec { + let mut names: Vec<_> = editor .documents .iter() .map(|(_id, doc)| { @@ -214,7 +212,7 @@ pub mod completers { names } - pub fn theme(_cx: &Context, input: &str) -> Vec { + pub fn theme(_editor: &Editor, input: &str) -> Vec { let mut names = theme::Loader::read_names(&helix_core::runtime_dir().join("themes")); names.extend(theme::Loader::read_names( &helix_core::config_dir().join("themes"), @@ -242,7 +240,7 @@ pub mod completers { names } - pub fn setting(_cx: &Context, input: &str) -> Vec { + pub fn setting(_editor: &Editor, input: &str) -> Vec { static KEYS: Lazy> = Lazy::new(|| { serde_json::to_value(Config::default()) .unwrap() @@ -267,7 +265,7 @@ pub mod completers { .collect() } - pub fn filename(_cx: &Context, input: &str) -> Vec { + pub fn filename(_editor: &Editor, input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); @@ -279,7 +277,7 @@ pub mod completers { }) } - pub fn directory(_cx: &Context, input: &str) -> Vec { + pub fn directory(_editor: &Editor, input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index dcc6400266f9..622af3876334 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -1,7 +1,7 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, ctrl, key, shift, - ui::EditorView, + ui::{self, EditorView}, }; use crossterm::event::Event; use tui::{ @@ -302,7 +302,7 @@ impl Picker { let prompt = Prompt::new( "".into(), None, - |_ctx: &Context, _pattern: &str| Vec::new(), + ui::completers::none, |_editor: &mut Context, _pattern: &str, _event: PromptEvent| { // }, diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index ff6b8c76e6b6..18b390dd1403 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -24,7 +24,7 @@ pub struct Prompt { selection: Option, history_register: Option, history_pos: Option, - completion_fn: Box Vec>, + completion_fn: Box Vec>, callback_fn: Box, pub doc_fn: Box Option<&'static str>>, } @@ -59,7 +59,7 @@ impl Prompt { pub fn new( prompt: Cow<'static, str>, history_register: Option, - completion_fn: impl FnMut(&Context, &str) -> Vec + 'static, + completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, ) -> Self { Self { @@ -76,6 +76,10 @@ impl Prompt { } } + pub fn recalculate_completion(&mut self, editor: &Editor) { + self.completion = (self.completion_fn)(editor, &self.line); + } + /// Compute the cursor position after applying movement /// Taken from: https://github.com/wez/wezterm/blob/e0b62d07ca9bf8ce69a61e30a3c20e7abc48ce7e/termwiz/src/lineedit/mod.rs#L516-L611 fn eval_movement(&self, movement: Movement) -> usize { @@ -183,7 +187,7 @@ impl Prompt { if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { self.cursor = pos; } - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); self.exit_selection(); } @@ -211,7 +215,7 @@ impl Prompt { self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn delete_char_forwards(&mut self, cx: &Context) { @@ -219,7 +223,7 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn delete_word_backwards(&mut self, cx: &Context) { @@ -228,7 +232,7 @@ impl Prompt { self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn delete_word_forwards(&mut self, cx: &Context) { @@ -236,7 +240,7 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn kill_to_start_of_line(&mut self, cx: &Context) { @@ -245,7 +249,7 @@ impl Prompt { self.cursor = pos; self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn kill_to_end_of_line(&mut self, cx: &Context) { @@ -253,13 +257,13 @@ impl Prompt { self.line.replace_range(self.cursor..pos, ""); self.exit_selection(); - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); } pub fn clear(&mut self, cx: &Context) { self.line.clear(); self.cursor = 0; - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); self.exit_selection(); } @@ -474,7 +478,7 @@ impl Component for Prompt { } key!(Enter) => { if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) { - self.completion = (self.completion_fn)(cx, &self.line); + self.recalculate_completion(cx.editor); self.exit_selection(); } else { (self.callback_fn)(cx, &self.line, PromptEvent::Validate); @@ -525,7 +529,6 @@ impl Component for Prompt { } fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { - self.completion = (self.completion_fn)(cx, &self.line); self.render_prompt(area, surface, cx) } From 3df2a30b207c1707d88916cd2ffa841ffce9580d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 17 Feb 2022 13:56:01 +0900 Subject: [PATCH 5/5] Manually recalculate initial completion where it matters --- helix-term/src/commands.rs | 2 ++ helix-term/src/ui/mod.rs | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9cf5630da2a1..c07f44dcccf7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3393,6 +3393,8 @@ fn command_mode(cx: &mut Context) { None }); + // Calculate initial completion + prompt.recalculate_completion(cx.editor); cx.push_layer(Box::new(prompt)); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index e9ff9d1a3408..e269c8eddf07 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -38,7 +38,7 @@ pub fn regex_prompt( let snapshot = doc.selection(view_id).clone(); let offset_snapshot = view.offset; - Prompt::new( + let mut prompt = Prompt::new( prompt, history_register, completion_fn, @@ -91,7 +91,10 @@ pub fn regex_prompt( } } }, - ) + ); + // Calculate initial completion + prompt.recalculate_completion(cx.editor); + prompt } pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker {