diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 7a50e007b9b9..a2a6c7ea2ceb 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -193,7 +193,7 @@ impl Application { if first.is_dir() { std::env::set_current_dir(first).context("set current dir")?; editor.new_file(Action::VerticalSplit); - let picker = ui::file_picker(".".into(), &config.load().editor); + let picker = ui::file_picker(".".into(), None, &config.load().editor); compositor.push(Box::new(overlayed(picker))); } else { let nr_of_files = args.files.len(); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2bac5be08e8d..3a4ac66ededc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1839,7 +1839,7 @@ fn make_search_word_bounded(cx: &mut Context) { } fn global_search(cx: &mut Context) { - #[derive(Debug)] + #[derive(Debug, PartialEq)] struct FileResult { path: PathBuf, /// 0 indexed lines @@ -2007,6 +2007,7 @@ fn global_search(cx: &mut Context) { |_editor, FileResult { path, line_num }| { Some((path.clone().into(), Some((*line_num, *line_num)))) }, + None, ); compositor.push(Box::new(overlayed(picker))); }, @@ -2286,19 +2287,24 @@ fn file_picker(cx: &mut Context) { // We don't specify language markers, root will be the root of the current // git repo or the current dir if we're not in a repo let root = find_root(None, &[]); - let picker = ui::file_picker(root, &cx.editor.config()); + + let doc = doc!(cx.editor); + let picker = ui::file_picker(root, doc.path().cloned(), &cx.editor.config()); cx.push_layer(Box::new(overlayed(picker))); } fn file_picker_in_current_directory(cx: &mut Context) { let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./")); - let picker = ui::file_picker(cwd, &cx.editor.config()); + + let doc = doc!(cx.editor); + let picker = ui::file_picker(cwd, doc.path().cloned(), &cx.editor.config()); cx.push_layer(Box::new(overlayed(picker))); } fn buffer_picker(cx: &mut Context) { let current = view!(cx.editor).doc; + #[derive(PartialEq)] struct BufferMeta { id: DocumentId, path: Option, @@ -2362,11 +2368,13 @@ fn buffer_picker(cx: &mut Context) { .cursor_line(doc.text().slice(..)); Some((meta.id.into(), Some((line, line)))) }, + None, ); cx.push_layer(Box::new(overlayed(picker))); } fn jumplist_picker(cx: &mut Context) { + #[derive(PartialEq)] struct JumpMeta { id: DocumentId, path: Option, @@ -2444,6 +2452,7 @@ fn jumplist_picker(cx: &mut Context) { let line = meta.selection.primary().cursor_line(doc.text().slice(..)); Some((meta.path.clone()?.into(), Some((line, line)))) }, + None, ); cx.push_layer(Box::new(overlayed(picker))); } diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index b182f28c4284..cfffea02a3bb 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -87,6 +87,7 @@ fn thread_picker( )); Some((path.into(), pos)) }, + None, ); compositor.push(Box::new(picker)); }, @@ -714,6 +715,7 @@ pub fn dap_switch_stack_frame(cx: &mut Context) { ) }) }, + None, ); cx.push_layer(Box::new(picker)) } diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 810e3adf1115..3a2debd8cacf 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -100,6 +100,7 @@ struct DiagnosticStyles { error: Style, } +#[derive(PartialEq)] struct PickerDiagnostic { url: lsp::Url, diag: lsp::Diagnostic, @@ -241,6 +242,7 @@ fn sym_picker( } }, move |_editor, symbol| Some(location_to_file_location(&symbol.location)), + None, ) .truncate_start(false) } @@ -304,6 +306,7 @@ fn diag_picker( let location = lsp::Location::new(url.clone(), diag.range); Some(location_to_file_location(&location)) }, + None, ) .truncate_start(false) } @@ -854,6 +857,7 @@ fn goto_impl( jump_to_location(cx.editor, location, offset_encoding, action) }, move |_editor, location| Some(location_to_file_location(location)), + None, ); compositor.push(Box::new(overlayed(picker))); } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index b9c1f9ded2e1..692f00f4b1c6 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -14,7 +14,7 @@ use fuzzy_matcher::FuzzyMatcher; use helix_view::{graphics::Rect, Editor}; use tui::layout::Constraint; -pub trait Item { +pub trait Item: PartialEq { /// Additional editor state that is used for label calculation. type Data; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f61c4c450c7d..2d7fce43a2b0 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -156,7 +156,11 @@ pub fn regex_prompt( cx.push_layer(Box::new(prompt)); } -pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker { +pub fn file_picker( + root: PathBuf, + default_path: Option, + config: &helix_view::editor::Config, +) -> FilePicker { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time::Instant; @@ -215,6 +219,12 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi files.take(MAX).collect() }; + // Enforce the correct behavior based on the configuration. + let default_path = match default_path { + Some(path) if config.file_picker.highlight_current => Some(path), + _ => None, + }; + log::debug!("file_picker init {:?}", Instant::now().duration_since(now)); FilePicker::new( @@ -231,6 +241,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi } }, |_editor, path| Some((path.clone().into(), None)), + default_path, ) } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 5e9ca3d887a5..b23ed21e700c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -112,9 +112,11 @@ impl FilePicker { editor_data: T::Data, callback_fn: impl Fn(&mut Context, &T, Action) + 'static, preview_fn: impl Fn(&Editor, &T) -> Option + 'static, + initial_cursor: Option, ) -> Self { let truncate_start = true; - let mut picker = Picker::new(options, editor_data, callback_fn); + let mut picker = + Picker::new(options, editor_data, callback_fn).with_default_item(initial_cursor); picker.truncate_start = truncate_start; Self { @@ -365,6 +367,7 @@ impl Ord for PickerMatch { pub struct Picker { options: Vec, + default_item: Option, editor_data: T::Data, // filter: String, matcher: Box, @@ -410,7 +413,9 @@ impl Picker { show_preview: true, callback_fn: Box::new(callback_fn), completion_height: 0, + default_item: None, }; + picker.cursor = picker.find_default_item_index().unwrap_or(0); // scoring on empty input: // TODO: just reuse score() @@ -428,6 +433,11 @@ impl Picker { picker } + pub fn with_default_item(mut self, default_item: Option) -> Self { + self.default_item = default_item; + self + } + pub fn score(&mut self) { let now = Instant::now(); @@ -437,7 +447,7 @@ impl Picker { return; } - if pattern.is_empty() { + let cursor = if pattern.is_empty() { // Fast path for no pattern. self.matches.clear(); self.matches @@ -449,6 +459,8 @@ impl Picker { len: text.chars().count(), } })); + + self.find_default_item_index().unwrap_or(0) } else if pattern.starts_with(&self.previous_pattern) { let query = FuzzyQuery::new(pattern); // optimization: if the pattern is a more specific version of the previous one @@ -468,6 +480,7 @@ impl Picker { }); self.matches.sort_unstable(); + 0 } else { let query = FuzzyQuery::new(pattern); self.matches.clear(); @@ -488,15 +501,26 @@ impl Picker { }), ); self.matches.sort_unstable(); - } + 0 + }; log::debug!("picker score {:?}", Instant::now().duration_since(now)); // reset cursor position - self.cursor = 0; + self.cursor = cursor; self.previous_pattern.clone_from(pattern); } + fn find_default_item_index(&self) -> Option { + self.default_item + .as_ref() + .and_then(|default_item| self.find_item_index(default_item)) + } + + fn find_item_index(&self, item: &T) -> Option { + self.options.iter().position(|option| item.eq(option)) + } + /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`) pub fn move_by(&mut self, amount: usize, direction: Direction) { let len = self.matches.len(); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 973cf82ea109..ca8097b30a8e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -98,6 +98,9 @@ pub struct FilePickerConfig { /// WalkBuilder options /// Maximum Depth to recurse directories in file picker and global search. Defaults to `None`. pub max_depth: Option, + /// FilePicker option + /// If set the FilePicker selects the current doc's path by default from the list, if possible. + pub highlight_current: bool, } impl Default for FilePickerConfig { @@ -111,6 +114,7 @@ impl Default for FilePickerConfig { git_global: true, git_exclude: true, max_depth: None, + highlight_current: true, } } }