From b89a39bcb3bd2bf69bf936bf846992b2f68a4525 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 1 Feb 2022 15:11:20 +0100 Subject: [PATCH] Filter and sort suggestions in autocomplete Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 + crates/editor/Cargo.toml | 2 + crates/editor/src/editor.rs | 197 ++++++++++++++++++---------- crates/editor/src/movement.rs | 122 ++++------------- crates/editor/src/multi_buffer.rs | 49 +++++++ crates/find/src/find.rs | 2 +- crates/fuzzy/src/fuzzy.rs | 4 + crates/gpui/src/fonts.rs | 15 ++- crates/language/src/buffer.rs | 32 ++++- crates/theme/src/theme.rs | 1 + crates/zed/assets/themes/_base.toml | 3 +- 11 files changed, 258 insertions(+), 171 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7c811bc53..5de5b7ce1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1547,12 +1547,14 @@ dependencies = [ "collections", "ctor", "env_logger", + "fuzzy", "gpui", "itertools", "language", "lazy_static", "log", "lsp", + "ordered-float", "parking_lot", "postage", "project", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 6450db7d39..2ce10b4d81 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -19,6 +19,7 @@ test-support = [ text = { path = "../text" } clock = { path = "../clock" } collections = { path = "../collections" } +fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } language = { path = "../language" } project = { path = "../project" } @@ -31,6 +32,7 @@ anyhow = "1.0" itertools = "0.10" lazy_static = "1.4" log = "0.4" +ordered-float = "2.1.1" parking_lot = "0.11" postage = { version = "0.4", features = ["futures-traits"] } rand = { version = "0.8.3", optional = true } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cfc00fa7e6..9cdb670067 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13,6 +13,7 @@ use collections::{BTreeMap, HashMap, HashSet}; pub use display_map::DisplayPoint; use display_map::*; pub use element::*; +use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ action, color::Color, @@ -31,16 +32,17 @@ use language::{ }; use multi_buffer::MultiBufferChunks; pub use multi_buffer::{ - Anchor, AnchorRangeExt, ExcerptId, ExcerptProperties, MultiBuffer, MultiBufferSnapshot, - ToOffset, ToPoint, + char_kind, Anchor, AnchorRangeExt, CharKind, ExcerptId, ExcerptProperties, MultiBuffer, + MultiBufferSnapshot, ToOffset, ToPoint, }; +use ordered_float::OrderedFloat; use postage::watch; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use smol::Timer; use std::{ any::TypeId, - cmp::{self, Ordering}, + cmp::{self, Ordering, Reverse}, iter::{self, FromIterator}, mem, ops::{Deref, Range, RangeInclusive, Sub}, @@ -50,7 +52,7 @@ use std::{ pub use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; -use util::post_inc; +use util::{post_inc, ResultExt}; use workspace::{ItemNavHistory, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -430,6 +432,7 @@ struct BracketPairState { struct CompletionState { initial_position: Anchor, completions: Arc<[Completion]>, + matches: Arc<[StringMatch]>, selected_item: usize, list: UniformListState, } @@ -453,14 +456,6 @@ pub struct NavigationData { offset: usize, } -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] -pub enum CharKind { - Newline, - Punctuation, - Whitespace, - Word, -} - impl Editor { pub fn single_line(build_settings: BuildSettings, cx: &mut ViewContext) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); @@ -888,7 +883,7 @@ impl Editor { mode = SelectMode::Character; } 2 => { - let (range, _) = movement::surrounding_word(&display_map, position); + let range = movement::surrounding_word(&display_map, position); start = buffer.anchor_before(range.start.to_point(&display_map)); end = buffer.anchor_before(range.end.to_point(&display_map)); mode = SelectMode::Word(start.clone()..end.clone()); @@ -991,7 +986,7 @@ impl Editor { if movement::is_inside_word(&display_map, position) || original_display_range.contains(&position) { - let (word_range, _) = movement::surrounding_word(&display_map, position); + let word_range = movement::surrounding_word(&display_map, position); if word_range.start < original_display_range.start { head = word_range.start.to_point(&display_map); } else { @@ -1538,27 +1533,90 @@ impl Editor { return; }; + let query = { + let buffer = self.buffer.read(cx).read(cx); + let offset = position.to_offset(&buffer); + let (word_range, kind) = buffer.surrounding_word(offset); + if offset > word_range.start && kind == Some(CharKind::Word) { + Some( + buffer + .text_for_range(word_range.start..offset) + .collect::(), + ) + } else { + None + } + }; + let completions = self .buffer .update(cx, |buffer, cx| buffer.completions(position.clone(), cx)); cx.spawn_weak(|this, mut cx| async move { let completions = completions.await?; - if !completions.is_empty() { - if let Some(this) = cx.read(|cx| this.upgrade(cx)) { - this.update(&mut cx, |this, cx| { - if this.focused { - this.completion_state = Some(CompletionState { - initial_position: position, - completions: completions.into(), - selected_item: 0, - list: Default::default(), - }); - cx.notify(); - } - }); + let candidates = completions + .iter() + .enumerate() + .map(|(id, completion)| { + StringMatchCandidate::new( + id, + completion.label()[completion.filter_range()].into(), + ) + }) + .collect::>(); + let mut matches = if let Some(query) = query.as_ref() { + fuzzy::match_strings( + &candidates, + query, + false, + 100, + &Default::default(), + cx.background(), + ) + .await + } else { + candidates + .into_iter() + .enumerate() + .map(|(candidate_id, candidate)| StringMatch { + candidate_id, + score: Default::default(), + positions: Default::default(), + string: candidate.string, + }) + .collect() + }; + matches.sort_unstable_by_key(|mat| { + ( + Reverse(OrderedFloat(mat.score)), + completions[mat.candidate_id].sort_key(), + ) + }); + + for mat in &mut matches { + let filter_start = completions[mat.candidate_id].filter_range().start; + for position in &mut mat.positions { + *position += filter_start; } } + + if let Some(this) = cx.read(|cx| this.upgrade(cx)) { + this.update(&mut cx, |this, cx| { + if matches.is_empty() { + this.completion_state.take(); + } else if this.focused { + this.completion_state = Some(CompletionState { + initial_position: position, + completions: completions.into(), + matches: matches.into(), + selected_item: 0, + list: Default::default(), + }); + } + + cx.notify(); + }); + } Ok::<_, anyhow::Error>(()) }) .detach_and_log_err(cx); @@ -1590,21 +1648,33 @@ impl Editor { let build_settings = self.build_settings.clone(); let settings = build_settings(cx); let completions = state.completions.clone(); + let matches = state.matches.clone(); let selected_item = state.selected_item; UniformList::new( state.list.clone(), - state.completions.len(), + matches.len(), move |range, items, cx| { let settings = build_settings(cx); let start_ix = range.start; - for (ix, completion) in completions[range].iter().enumerate() { + let label_style = LabelStyle { + text: settings.style.text.clone(), + highlight_text: settings + .style + .text + .clone() + .highlight(settings.style.autocomplete.match_highlight, cx.font_cache()) + .log_err(), + }; + for (ix, mat) in matches[range].iter().enumerate() { let item_style = if start_ix + ix == selected_item { settings.style.autocomplete.selected_item } else { settings.style.autocomplete.item }; + let completion = &completions[mat.candidate_id]; items.push( - Label::new(completion.label().to_string(), settings.style.text.clone()) + Label::new(completion.label().to_string(), label_style.clone()) + .with_highlights(mat.positions.clone()) .contained() .with_style(item_style) .boxed(), @@ -1614,10 +1684,12 @@ impl Editor { ) .with_width_from_item( state - .completions + .matches .iter() .enumerate() - .max_by_key(|(_, completion)| completion.label().chars().count()) + .max_by_key(|(_, mat)| { + state.completions[mat.candidate_id].label().chars().count() + }) .map(|(ix, _)| ix), ) .contained() @@ -2914,7 +2986,7 @@ impl Editor { } else if selections.len() == 1 { let selection = selections.last_mut().unwrap(); if selection.start == selection.end { - let (word_range, _) = movement::surrounding_word( + let word_range = movement::surrounding_word( &display_map, selection.start.to_display_point(&display_map), ); @@ -3534,8 +3606,7 @@ impl Editor { ) where T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug, { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let buffer = &display_map.buffer_snapshot; + let buffer = self.buffer.read(cx).snapshot(cx); let old_cursor_position = self.newest_anchor_selection().map(|s| s.head()); selections.sort_unstable_by_key(|s| s.start); @@ -3580,29 +3651,14 @@ impl Editor { } } - let new_cursor_position = selections - .iter() - .max_by_key(|s| s.id) - .map(|s| s.head().to_point(&buffer)); + let new_cursor_position = selections.iter().max_by_key(|s| s.id).map(|s| s.head()); if let Some(old_cursor_position) = old_cursor_position { - if new_cursor_position.is_some() { - self.push_to_nav_history(old_cursor_position, new_cursor_position, cx); - } - } - - if let Some((completion_state, cursor_position)) = - self.completion_state.as_ref().zip(new_cursor_position) - { - let cursor_position = cursor_position.to_display_point(&display_map); - let initial_position = completion_state - .initial_position - .to_display_point(&display_map); - - let (word_range, kind) = movement::surrounding_word(&display_map, initial_position); - if kind != Some(CharKind::Word) || !word_range.to_inclusive().contains(&cursor_position) - { - self.completion_state.take(); - cx.notify(); + if let Some(new_cursor_position) = new_cursor_position { + self.push_to_nav_history( + old_cursor_position, + Some(new_cursor_position.to_point(&buffer)), + cx, + ); } } @@ -3628,6 +3684,21 @@ impl Editor { })), cx, ); + + if let Some((completion_state, cursor_position)) = + self.completion_state.as_ref().zip(new_cursor_position) + { + let cursor_position = cursor_position.to_offset(&buffer); + let (word_range, kind) = + buffer.surrounding_word(completion_state.initial_position.clone()); + if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) + { + self.show_completions(&ShowCompletions, cx); + } else { + self.completion_state.take(); + cx.notify(); + } + } } /// Compute new ranges for any selections that were located in excerpts that have @@ -4424,18 +4495,6 @@ pub fn settings_builder( }) } -pub fn char_kind(c: char) -> CharKind { - if c == '\n' { - CharKind::Newline - } else if c.is_whitespace() { - CharKind::Whitespace - } else if c.is_alphanumeric() || c == '_' { - CharKind::Word - } else { - CharKind::Punctuation - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 36b384fadb..7eee1e627c 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -1,7 +1,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{char_kind, CharKind, ToPoint}; use anyhow::Result; -use std::{cmp, ops::Range}; +use std::ops::Range; pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> Result { if point.column() > 0 { @@ -183,42 +183,20 @@ pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word)) } -pub fn surrounding_word( - map: &DisplaySnapshot, - point: DisplayPoint, -) -> (Range, Option) { - let mut start = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); - let mut end = start; - - let text = &map.buffer_snapshot; - let mut next_chars = text.chars_at(start).peekable(); - let mut prev_chars = text.reversed_chars_at(start).peekable(); - let word_kind = cmp::max( - prev_chars.peek().copied().map(char_kind), - next_chars.peek().copied().map(char_kind), - ); - - for ch in prev_chars { - if Some(char_kind(ch)) == word_kind { - start -= ch.len_utf8(); - } else { - break; - } - } - - for ch in next_chars { - if Some(char_kind(ch)) == word_kind { - end += ch.len_utf8(); - } else { - break; - } - } - - ( - start.to_point(&map.buffer_snapshot).to_display_point(map) - ..end.to_point(&map.buffer_snapshot).to_display_point(map), - word_kind, - ) +pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range { + let position = map + .clip_point(position, Bias::Left) + .to_offset(map, Bias::Left); + let (range, _) = map.buffer_snapshot.surrounding_word(position); + let start = range + .start + .to_point(&map.buffer_snapshot) + .to_display_point(map); + let end = range + .end + .to_point(&map.buffer_snapshot) + .to_display_point(map); + start..end } #[cfg(test)] @@ -412,101 +390,59 @@ mod tests { assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 0)), - ( - DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 2)), - ( - DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 5)), - ( - DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 5), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 6)), - ( - DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 7)), - ( - DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 11)), - ( - DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 6)..DisplayPoint::new(0, 11), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 13)), - ( - DisplayPoint::new(0, 11)..DisplayPoint::new(0, 14), - Some(CharKind::Whitespace) - ) + DisplayPoint::new(0, 11)..DisplayPoint::new(0, 14), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 14)), - ( - DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 17)), - ( - DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(0, 19)), - ( - DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), - Some(CharKind::Word) - ) + DisplayPoint::new(0, 14)..DisplayPoint::new(0, 19), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(1, 0)), - ( - DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4), - Some(CharKind::Whitespace) - ) + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(1, 1)), - ( - DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4), - Some(CharKind::Whitespace) - ) + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 4), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(1, 6)), - ( - DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7), - Some(CharKind::Word) - ) + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7), ); assert_eq!( surrounding_word(&snapshot, DisplayPoint::new(1, 7)), - ( - DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7), - Some(CharKind::Word) - ) + DisplayPoint::new(1, 4)..DisplayPoint::new(1, 7), ); } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index ccae933517..eafb87d9c1 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -50,6 +50,14 @@ struct History { group_interval: Duration, } +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] +pub enum CharKind { + Newline, + Punctuation, + Whitespace, + Word, +} + struct Transaction { id: usize, buffer_transactions: HashSet<(usize, text::TransactionId)>, @@ -1155,6 +1163,35 @@ impl MultiBufferSnapshot { .eq(needle.bytes()) } + pub fn surrounding_word(&self, start: T) -> (Range, Option) { + let mut start = start.to_offset(self); + let mut end = start; + let mut next_chars = self.chars_at(start).peekable(); + let mut prev_chars = self.reversed_chars_at(start).peekable(); + let word_kind = cmp::max( + prev_chars.peek().copied().map(char_kind), + next_chars.peek().copied().map(char_kind), + ); + + for ch in prev_chars { + if Some(char_kind(ch)) == word_kind { + start -= ch.len_utf8(); + } else { + break; + } + } + + for ch in next_chars { + if Some(char_kind(ch)) == word_kind { + end += ch.len_utf8(); + } else { + break; + } + } + + (start..end, word_kind) + } + fn as_singleton(&self) -> Option<&Excerpt> { if self.singleton { self.excerpts.iter().next() @@ -2418,6 +2455,18 @@ impl ToPoint for Point { } } +pub fn char_kind(c: char) -> CharKind { + if c == '\n' { + CharKind::Newline + } else if c.is_whitespace() { + CharKind::Whitespace + } else if c.is_alphanumeric() || c == '_' { + CharKind::Word + } else { + CharKind::Punctuation + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/find/src/find.rs b/crates/find/src/find.rs index 571d9f4c4b..859e9f4923 100644 --- a/crates/find/src/find.rs +++ b/crates/find/src/find.rs @@ -296,7 +296,7 @@ impl FindBar { let mut text: String; if selection.start == selection.end { let point = selection.start.to_display_point(&display_map); - let (range, _) = editor::movement::surrounding_word(&display_map, point); + let range = editor::movement::surrounding_word(&display_map, point); let range = range.start.to_offset(&display_map, Bias::Left) ..range.end.to_offset(&display_map, Bias::Right); text = display_map.buffer_snapshot.text_for_range(range).collect(); diff --git a/crates/fuzzy/src/fuzzy.rs b/crates/fuzzy/src/fuzzy.rs index dbbef435f6..7458f27c91 100644 --- a/crates/fuzzy/src/fuzzy.rs +++ b/crates/fuzzy/src/fuzzy.rs @@ -181,6 +181,10 @@ pub async fn match_strings( cancel_flag: &AtomicBool, background: Arc, ) -> Vec { + if candidates.is_empty() { + return Default::default(); + } + let lowercase_query = query.to_lowercase().chars().collect::>(); let query = query.chars().collect::>(); diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 2768b9f986..7dc1173048 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -5,7 +5,7 @@ use crate::{ text_layout::RunStyle, FontCache, }; -use anyhow::anyhow; +use anyhow::{anyhow, Result}; pub use font_kit::{ metrics::Metrics, properties::{Properties, Stretch, Style, Weight}, @@ -107,7 +107,7 @@ impl TextStyle { underline: Option, color: Color, font_cache: &FontCache, - ) -> anyhow::Result { + ) -> Result { let font_family_name = font_family_name.into(); let font_family_id = font_cache.load_family(&[&font_family_name])?; let font_id = font_cache.select_font(font_family_id, &font_properties)?; @@ -127,6 +127,15 @@ impl TextStyle { self } + pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result { + if self.font_properties != style.font_properties { + self.font_id = font_cache.select_font(self.font_family_id, &style.font_properties)?; + } + self.color = style.color; + self.underline = style.underline; + Ok(self) + } + pub fn to_run(&self) -> RunStyle { RunStyle { font_id: self.font_id, @@ -135,7 +144,7 @@ impl TextStyle { } } - fn from_json(json: TextStyleJson) -> anyhow::Result { + fn from_json(json: TextStyleJson) -> Result { FONT_CACHE.with(|font_cache| { if let Some(font_cache) = font_cache.borrow().as_ref() { let font_properties = properties_from_json(json.weight, json.italic); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 446bd60b87..5b66d4c8e8 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -114,6 +114,7 @@ pub struct Diagnostic { pub is_disk_based: bool, } +#[derive(Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, @@ -230,7 +231,7 @@ impl File for FakeFile { } fn path(&self) -> &Arc { - &self.path + &self.path } fn full_path(&self, _: &AppContext) -> PathBuf { @@ -255,8 +256,11 @@ impl File for FakeFile { cx.spawn(|_| async move { Ok((Default::default(), SystemTime::UNIX_EPOCH)) }) } - fn format_remote(&self, buffer_id: u64, cx: &mut MutableAppContext) - -> Option>> { + fn format_remote( + &self, + buffer_id: u64, + cx: &mut MutableAppContext, + ) -> Option>> { None } @@ -1759,7 +1763,7 @@ impl Buffer { }; let old_range = this.anchor_before(old_range.start)..this.anchor_after(old_range.end); - + Some(Completion { old_range, new_text, @@ -2511,6 +2515,26 @@ impl Completion { pub fn label(&self) -> &str { &self.lsp_completion.label } + + pub fn filter_range(&self) -> Range { + if let Some(filter_text) = self.lsp_completion.filter_text.as_deref() { + if let Some(start) = self.label().find(filter_text) { + start..start + filter_text.len() + } else { + 0..self.label().len() + } + } else { + 0..self.label().len() + } + } + + pub fn sort_key(&self) -> (usize, &str) { + let kind_key = match self.lsp_completion.kind { + Some(lsp::CompletionItemKind::VARIABLE) => 0, + _ => 1, + }; + (kind_key, &self.label()[self.filter_range()]) + } } pub fn contiguous_ranges( diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index aca5e7d245..d097e1b003 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -328,6 +328,7 @@ pub struct AutocompleteStyle { pub container: ContainerStyle, pub item: ContainerStyle, pub selected_item: ContainerStyle, + pub match_highlight: HighlightStyle, } #[derive(Clone, Copy, Default, Deserialize)] diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 6a0c98c67f..2475dd9531 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -188,7 +188,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -318,6 +318,7 @@ message.highlight_text.color = "$text.3.color" background = "$surface.2" border = { width = 1, color = "$border.1" } item.padding = 2 +match_highlight = { color = "$editor.syntax.keyword.color", weight = "$editor.syntax.keyword.weight" } [editor.autocomplete.selected_item] extends = "$editor.autocomplete.item"