diff options
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/completion.rs | 23 | ||||
-rw-r--r-- | helix-term/src/ui/menu.rs | 54 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 7 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 38 |
4 files changed, 74 insertions, 48 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 38005aad..a3637415 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -2,6 +2,7 @@ use crate::compositor::{Component, Context, EventResult}; use crossterm::event::{Event, KeyCode, KeyEvent}; use helix_view::editor::CompleteAction; use tui::buffer::Buffer as Surface; +use tui::text::Spans; use std::borrow::Cow; @@ -15,19 +16,25 @@ use helix_lsp::{lsp, util}; use lsp::CompletionItem; impl menu::Item for CompletionItem { - fn sort_text(&self) -> &str { - self.filter_text.as_ref().unwrap_or(&self.label).as_str() + type Data = (); + fn sort_text(&self, data: &Self::Data) -> Cow<str> { + self.filter_text(data) } - fn filter_text(&self) -> &str { - self.filter_text.as_ref().unwrap_or(&self.label).as_str() + #[inline] + fn filter_text(&self, _data: &Self::Data) -> Cow<str> { + self.filter_text + .as_ref() + .unwrap_or(&self.label) + .as_str() + .into() } - fn label(&self) -> &str { - self.label.as_str() + fn label(&self, _data: &Self::Data) -> Spans { + self.label.as_str().into() } - fn row(&self) -> menu::Row { + fn row(&self, _data: &Self::Data) -> menu::Row { menu::Row::new(vec![ menu::Cell::from(self.label.as_str()), menu::Cell::from(match self.kind { @@ -85,7 +92,7 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Self { - let menu = Menu::new(items, move |editor: &mut Editor, item, event| { + let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| { fn item_to_transaction( doc: &Document, item: &CompletionItem, diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 0519374a..6bb64139 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -1,9 +1,11 @@ +use std::{borrow::Cow, path::PathBuf}; + use crate::{ compositor::{Callback, Component, Compositor, Context, EventResult}, ctrl, key, shift, }; use crossterm::event::Event; -use tui::{buffer::Buffer as Surface, widgets::Table}; +use tui::{buffer::Buffer as Surface, text::Spans, widgets::Table}; pub use tui::widgets::{Cell, Row}; @@ -14,22 +16,41 @@ use helix_view::{graphics::Rect, Editor}; use tui::layout::Constraint; pub trait Item { - fn label(&self) -> &str; + /// Additional editor state that is used for label calculation. + type Data; + + fn label(&self, data: &Self::Data) -> Spans; + + fn sort_text(&self, data: &Self::Data) -> Cow<str> { + let label: String = self.label(data).into(); + label.into() + } - fn sort_text(&self) -> &str { - self.label() + fn filter_text(&self, data: &Self::Data) -> Cow<str> { + let label: String = self.label(data).into(); + label.into() } - fn filter_text(&self) -> &str { - self.label() + + fn row(&self, data: &Self::Data) -> Row { + Row::new(vec![Cell::from(self.label(data))]) } +} - fn row(&self) -> Row { - Row::new(vec![Cell::from(self.label())]) +impl Item for PathBuf { + /// Root prefix to strip. + type Data = PathBuf; + + fn label(&self, root_path: &Self::Data) -> Spans { + self.strip_prefix(&root_path) + .unwrap_or(self) + .to_string_lossy() + .into() } } pub struct Menu<T: Item> { options: Vec<T>, + editor_data: T::Data, cursor: Option<usize>, @@ -54,10 +75,12 @@ impl<T: Item> Menu<T> { // rendering) pub fn new( options: Vec<T>, + editor_data: <T as Item>::Data, callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static, ) -> Self { let mut menu = Self { options, + editor_data, matcher: Box::new(Matcher::default()), matches: Vec::new(), cursor: None, @@ -83,16 +106,17 @@ impl<T: Item> Menu<T> { .iter() .enumerate() .filter_map(|(index, option)| { - let text = option.filter_text(); + let text: String = option.filter_text(&self.editor_data).into(); // TODO: using fuzzy_indices could give us the char idx for match highlighting self.matcher - .fuzzy_match(text, pattern) + .fuzzy_match(&text, pattern) .map(|score| (index, score)) }), ); // matches.sort_unstable_by_key(|(_, score)| -score); - self.matches - .sort_unstable_by_key(|(index, _score)| self.options[*index].sort_text()); + self.matches.sort_unstable_by_key(|(index, _score)| { + self.options[*index].sort_text(&self.editor_data) + }); // reset cursor position self.cursor = None; @@ -127,10 +151,10 @@ impl<T: Item> Menu<T> { let n = self .options .first() - .map(|option| option.row().cells.len()) + .map(|option| option.row(&self.editor_data).cells.len()) .unwrap_or_default(); let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| { - let row = option.row(); + let row = option.row(&self.editor_data); // maintain max for each column for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { let width = cell.content.width(); @@ -300,7 +324,7 @@ impl<T: Item + 'static> Component for Menu<T> { let scroll_line = (win_height - scroll_height) * scroll / std::cmp::max(1, len.saturating_sub(win_height)); - let rows = options.iter().map(|option| option.row()); + let rows = options.iter().map(|option| option.row(&self.editor_data)); let table = Table::new(rows) .style(style) .highlight_style(selected) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 948a5f2b..8d2bd325 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -23,8 +23,6 @@ pub use text::Text; use helix_core::regex::Regex; use helix_core::regex::RegexBuilder; use helix_view::{Document, Editor, View}; -use tui; -use tui::text::Spans; use std::path::PathBuf; @@ -172,10 +170,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi FilePicker::new( files, - move |path: &PathBuf| { - // format_fn - Spans::from(path.strip_prefix(&root).unwrap_or(path).to_string_lossy()) - }, + root, move |cx, path: &PathBuf, action| { if let Err(e) = cx.editor.open(path, action) { let err = if let Some(err) = e.source() { diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 1581b0a1..01fea718 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -6,7 +6,6 @@ use crate::{ use crossterm::event::Event; use tui::{ buffer::Buffer as Surface, - text::Spans, widgets::{Block, BorderType, Borders}, }; @@ -30,6 +29,8 @@ use helix_view::{ Document, Editor, }; +use super::menu::Item; + pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; @@ -37,7 +38,7 @@ pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; /// File path and range of lines (used to align and highlight lines) pub type FileLocation = (PathBuf, Option<(usize, usize)>); -pub struct FilePicker<T> { +pub struct FilePicker<T: Item> { picker: Picker<T>, pub truncate_start: bool, /// Caches paths to documents @@ -84,15 +85,15 @@ impl Preview<'_, '_> { } } -impl<T> FilePicker<T> { +impl<T: Item> FilePicker<T> { pub fn new( options: Vec<T>, - format_fn: impl Fn(&T) -> Spans + 'static, + editor_data: T::Data, callback_fn: impl Fn(&mut Context, &T, Action) + 'static, preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static, ) -> Self { let truncate_start = true; - let mut picker = Picker::new(options, format_fn, callback_fn); + let mut picker = Picker::new(options, editor_data, callback_fn); picker.truncate_start = truncate_start; Self { @@ -163,7 +164,7 @@ impl<T> FilePicker<T> { } } -impl<T: 'static> Component for FilePicker<T> { +impl<T: Item + 'static> Component for FilePicker<T> { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { // +---------+ +---------+ // |prompt | |preview | @@ -280,8 +281,9 @@ impl<T: 'static> Component for FilePicker<T> { } } -pub struct Picker<T> { +pub struct Picker<T: Item> { options: Vec<T>, + editor_data: T::Data, // filter: String, matcher: Box<Matcher>, /// (index, score) @@ -299,14 +301,13 @@ pub struct Picker<T> { /// Whether to truncate the start (default true) pub truncate_start: bool, - format_fn: Box<dyn Fn(&T) -> Spans>, callback_fn: Box<dyn Fn(&mut Context, &T, Action)>, } -impl<T> Picker<T> { +impl<T: Item> Picker<T> { pub fn new( options: Vec<T>, - format_fn: impl Fn(&T) -> Spans + 'static, + editor_data: T::Data, callback_fn: impl Fn(&mut Context, &T, Action) + 'static, ) -> Self { let prompt = Prompt::new( @@ -318,6 +319,7 @@ impl<T> Picker<T> { let mut picker = Self { options, + editor_data, matcher: Box::new(Matcher::default()), matches: Vec::new(), filters: Vec::new(), @@ -325,7 +327,6 @@ impl<T> Picker<T> { prompt, previous_pattern: String::new(), truncate_start: true, - format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), completion_height: 0, }; @@ -371,9 +372,9 @@ impl<T> Picker<T> { #[allow(unstable_name_collisions)] self.matches.retain_mut(|(index, score)| { let option = &self.options[*index]; - // TODO: maybe using format_fn isn't the best idea here - let line: String = (self.format_fn)(option).into(); - match self.matcher.fuzzy_match(&line, pattern) { + let text = option.sort_text(&self.editor_data); + + match self.matcher.fuzzy_match(&text, pattern) { Some(s) => { // Update the score *score = s; @@ -399,11 +400,10 @@ impl<T> Picker<T> { self.filters.binary_search(&index).ok()?; } - // TODO: maybe using format_fn isn't the best idea here - let line: String = (self.format_fn)(option).into(); + let text = option.filter_text(&self.editor_data); self.matcher - .fuzzy_match(&line, pattern) + .fuzzy_match(&text, pattern) .map(|score| (index, score)) }), ); @@ -477,7 +477,7 @@ impl<T> Picker<T> { // - on input change: // - score all the names in relation to input -impl<T: 'static> Component for Picker<T> { +impl<T: Item + 'static> Component for Picker<T> { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { self.completion_height = viewport.1.saturating_sub(4); Some(viewport) @@ -610,7 +610,7 @@ impl<T: 'static> Component for Picker<T> { surface.set_string(inner.x.saturating_sub(2), inner.y + i as u16, ">", selected); } - let spans = (self.format_fn)(option); + let spans = option.label(&self.editor_data); let (_score, highlights) = self .matcher .fuzzy_indices(&String::from(&spans), self.prompt.line()) |