diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 1ee4a01a9b47..b2fb5c97cdc8 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,7 +1,10 @@ use crate::compositor::{Component, Context, EventResult}; use crossterm::event::{Event, KeyCode, KeyEvent}; use helix_view::editor::CompleteAction; +use helix_view::theme::{Modifier, Style}; +use helix_view::Theme; use tui::buffer::Buffer as Surface; +use tui::text::{Span, Spans}; use std::borrow::Cow; @@ -27,38 +30,94 @@ impl menu::Item for CompletionItem { self.label.as_str() } - fn row(&self) -> menu::Row { + fn row(&self, data: Option<(&Theme, &Vec)>) -> menu::Row { + let (lsp_type_label, style) = match self.kind { + Some(lsp::CompletionItemKind::TEXT) => ("text", Some("ui.text")), + Some(lsp::CompletionItemKind::METHOD) => ("method", Some("function.method")), + Some(lsp::CompletionItemKind::FUNCTION) => ("function", Some("function")), + Some(lsp::CompletionItemKind::CONSTRUCTOR) => ("constructor", Some("constructor")), + Some(lsp::CompletionItemKind::FIELD) => ("field", Some("variable.other.member")), + Some(lsp::CompletionItemKind::VARIABLE) => ("variable", Some("variable")), + Some(lsp::CompletionItemKind::CLASS) => ("class", Some("type")), + Some(lsp::CompletionItemKind::INTERFACE) => ("interface", Some("type")), + Some(lsp::CompletionItemKind::MODULE) => ("module", Some("module")), + Some(lsp::CompletionItemKind::PROPERTY) => ("property", Some("attributes")), + Some(lsp::CompletionItemKind::UNIT) => ("unit", Some("constant")), + Some(lsp::CompletionItemKind::VALUE) => ("value", Some("string")), + Some(lsp::CompletionItemKind::ENUM) => ("enum", Some("type")), + Some(lsp::CompletionItemKind::KEYWORD) => ("keyword", Some("keyword")), + Some(lsp::CompletionItemKind::SNIPPET) => ("snippet", None), + Some(lsp::CompletionItemKind::COLOR) => ("color", None), + Some(lsp::CompletionItemKind::FILE) => ("file", None), + Some(lsp::CompletionItemKind::REFERENCE) => ("reference", None), + Some(lsp::CompletionItemKind::FOLDER) => ("folder", None), + Some(lsp::CompletionItemKind::ENUM_MEMBER) => { + ("enum_member", Some("type.enum.variant")) + } + Some(lsp::CompletionItemKind::CONSTANT) => ("constant", Some("constant")), + Some(lsp::CompletionItemKind::STRUCT) => ("struct", Some("type")), + Some(lsp::CompletionItemKind::EVENT) => ("event", None), + Some(lsp::CompletionItemKind::OPERATOR) => ("operator", Some("operator")), + Some(lsp::CompletionItemKind::TYPE_PARAMETER) => { + ("type_param", Some("function.parameter")) + } + Some(kind) => unimplemented!("{:?}", kind), + None => ("", None), + }; + + // fuzzy_indices is a vector of indices, which can be + // not consecutives. These are two valid fuzzy_indices vectors + // [0,1] + // [1,2,3,7,8,10] + let spans = if let Some((theme, fuzzy_indices)) = data { + let mut fuzzy_start: usize = fuzzy_indices + .first() + .map_or(fuzzy_indices.len(), |first| *first); + let mut fuzzy_end = 0; + // In most cases we have three spans, start_match_end + let mut results = Vec::with_capacity(3); + for i in 1..fuzzy_indices.len() { + // We hit the end of a group of consecutive indices + if fuzzy_indices[i - 1] + 1 != fuzzy_indices[i] { + results.push(Span::styled( + &self.label[fuzzy_end..fuzzy_start], + Style::default(), + )); + fuzzy_end = fuzzy_indices[i - 1] + 1; + results.push(Span::styled( + &self.label[fuzzy_start..fuzzy_end], + theme.get("special").add_modifier(Modifier::BOLD), + )); + fuzzy_start = fuzzy_indices[i]; + } + } + // Always add these three spans: start_match_end + results.push(Span::styled( + &self.label[fuzzy_end..fuzzy_start], + Style::default(), + )); + fuzzy_end = fuzzy_indices.last().map_or(0, |last| *last + 1); + results.push(Span::styled( + &self.label[fuzzy_start..fuzzy_end], + theme.get("special").add_modifier(Modifier::BOLD), + )); + results.push(Span::styled(&self.label[fuzzy_end..], Style::default())); + results + } else { + vec![Span::styled(self.label.as_str(), Style::default())] + }; + menu::Row::new(vec![ - menu::Cell::from(self.label.as_str()), - menu::Cell::from(match self.kind { - Some(lsp::CompletionItemKind::TEXT) => "text", - Some(lsp::CompletionItemKind::METHOD) => "method", - Some(lsp::CompletionItemKind::FUNCTION) => "function", - Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor", - Some(lsp::CompletionItemKind::FIELD) => "field", - Some(lsp::CompletionItemKind::VARIABLE) => "variable", - Some(lsp::CompletionItemKind::CLASS) => "class", - Some(lsp::CompletionItemKind::INTERFACE) => "interface", - Some(lsp::CompletionItemKind::MODULE) => "module", - Some(lsp::CompletionItemKind::PROPERTY) => "property", - Some(lsp::CompletionItemKind::UNIT) => "unit", - Some(lsp::CompletionItemKind::VALUE) => "value", - Some(lsp::CompletionItemKind::ENUM) => "enum", - Some(lsp::CompletionItemKind::KEYWORD) => "keyword", - Some(lsp::CompletionItemKind::SNIPPET) => "snippet", - Some(lsp::CompletionItemKind::COLOR) => "color", - Some(lsp::CompletionItemKind::FILE) => "file", - Some(lsp::CompletionItemKind::REFERENCE) => "reference", - Some(lsp::CompletionItemKind::FOLDER) => "folder", - Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member", - Some(lsp::CompletionItemKind::CONSTANT) => "constant", - Some(lsp::CompletionItemKind::STRUCT) => "struct", - Some(lsp::CompletionItemKind::EVENT) => "event", - Some(lsp::CompletionItemKind::OPERATOR) => "operator", - Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param", - Some(kind) => unimplemented!("{:?}", kind), - None => "", - }), + menu::Cell::from(Spans::from(spans)), + menu::Cell::from(lsp_type_label).style( + data.and_then(|(theme, _)| style.and_then(|style| theme.try_get(style))) + .map_or(Style::default(), |mut style| { + style.bg = None; + style + .remove_modifier(Modifier::BOLD) + .add_modifier(Modifier::ITALIC) + }), + ), // self.detail.as_deref().unwrap_or("") // self.label_details // .as_ref() diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index d67a429e37f3..2fd55804d65d 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -10,7 +10,7 @@ pub use tui::widgets::{Cell, Row}; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; -use helix_view::{graphics::Rect, Editor}; +use helix_view::{graphics::Rect, Editor, Theme}; use tui::layout::Constraint; pub trait Item { @@ -23,7 +23,7 @@ pub trait Item { self.label() } - fn row(&self) -> Row { + fn row(&self, _theme: Option<(&Theme, &Vec)>) -> Row { Row::new(vec![Cell::from(self.label())]) } } @@ -35,7 +35,7 @@ pub struct Menu { matcher: Box, /// (index, score) - matches: Vec<(usize, i64)>, + matches: Vec<(usize, i64, Vec)>, widths: Vec, @@ -84,13 +84,13 @@ impl Menu { let text = option.filter_text(); // TODO: using fuzzy_indices could give us the char idx for match highlighting self.matcher - .fuzzy_match(text, pattern) - .map(|score| (index, score)) + .fuzzy_indices(text, pattern) + .map(|(score, fuzzy_indices)| (index, score, fuzzy_indices)) }), ); // matches.sort_unstable_by_key(|(_, score)| -score); self.matches - .sort_unstable_by_key(|(index, _score)| self.options[*index].sort_text()); + .sort_unstable_by_key(|(index, _score, _)| self.options[*index].sort_text()); // reset cursor position self.cursor = None; @@ -125,10 +125,10 @@ impl Menu { let n = self .options .first() - .map(|option| option.row().cells.len()) + .map(|option| option.row(None).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(None); // maintain max for each column for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { let width = cell.content.width(); @@ -183,7 +183,7 @@ impl Menu { self.cursor.and_then(|cursor| { self.matches .get(cursor) - .map(|(index, _score)| &self.options[*index]) + .map(|(index, _score, _)| &self.options[*index]) }) } @@ -277,9 +277,9 @@ impl Component for Menu { let options: Vec<_> = self .matches .iter() - .map(|(index, _score)| { + .map(|(index, _score, fuzzy_indices)| { // (index, self.options.get(*index).unwrap()) // get_unchecked - &self.options[*index] // get_unchecked + (&self.options[*index], fuzzy_indices) // get_unchecked }) .collect(); @@ -296,7 +296,9 @@ impl Component for Menu { 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, fuzzy_indices)| option.row(Some((theme, fuzzy_indices)))); let table = Table::new(rows) .style(style) .highlight_style(selected)