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",
Antonio Scandurra and Nathan Sobo created
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
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(-)
@@ -1547,12 +1547,14 @@ dependencies = [
"collections",
"ctor",
"env_logger",
+ "fuzzy",
"gpui",
"itertools",
"language",
"lazy_static",
"log",
"lsp",
+ "ordered-float",
"parking_lot",
"postage",
"project",
@@ -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 }
@@ -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<Anchor>]>,
+ 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>) -> 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::<String>(),
+ )
+ } 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::<Vec<_>>();
+ 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::*;
@@ -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<DisplayPoint> {
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<DisplayPoint>, Option<CharKind>) {
- 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<DisplayPoint> {
+ 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),
);
}
}
@@ -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<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
+ 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::*;
@@ -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();
@@ -181,6 +181,10 @@ pub async fn match_strings(
cancel_flag: &AtomicBool,
background: Arc<executor::Background>,
) -> Vec<StringMatch> {
+ if candidates.is_empty() {
+ return Default::default();
+ }
+
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
@@ -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<Underline>,
color: Color,
font_cache: &FontCache,
- ) -> anyhow::Result<Self> {
+ ) -> Result<Self> {
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<Self> {
+ 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<Self> {
+ fn from_json(json: TextStyleJson) -> Result<Self> {
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);
@@ -114,6 +114,7 @@ pub struct Diagnostic {
pub is_disk_based: bool,
}
+#[derive(Debug)]
pub struct Completion<T> {
pub old_range: Range<T>,
pub new_text: String,
@@ -230,7 +231,7 @@ impl File for FakeFile {
}
fn path(&self) -> &Arc<Path> {
- &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<Task<Result<()>>> {
+ fn format_remote(
+ &self,
+ buffer_id: u64,
+ cx: &mut MutableAppContext,
+ ) -> Option<Task<Result<()>>> {
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<T> Completion<T> {
pub fn label(&self) -> &str {
&self.lsp_completion.label
}
+
+ pub fn filter_range(&self) -> Range<usize> {
+ 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(
@@ -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)]
@@ -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"