Detailed changes
@@ -20,7 +20,7 @@ use gpui::{
color::Color,
elements::*,
executor,
- fonts::TextStyle,
+ fonts::{self, HighlightStyle, TextStyle},
geometry::vector::{vec2f, Vector2F},
keymap::Binding,
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
@@ -489,7 +489,7 @@ impl CompletionState {
});
for mat in &mut matches {
- let filter_start = self.completions[mat.candidate_id].filter_range().start;
+ let filter_start = self.completions[mat.candidate_id].label.filter_range.start;
for position in &mut mat.positions {
*position += filter_start;
}
@@ -1628,7 +1628,7 @@ impl Editor {
.map(|(id, completion)| {
StringMatchCandidate::new(
id,
- completion.lsp_completion.label[completion.filter_range()].into(),
+ completion.label.text[completion.label.filter_range.clone()].into(),
)
})
.collect(),
@@ -1710,15 +1710,6 @@ impl Editor {
move |range, items, cx| {
let settings = build_settings(cx);
let start_ix = range.start;
- 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
@@ -1727,8 +1718,20 @@ impl Editor {
};
let completion = &completions[mat.candidate_id];
items.push(
- Label::new(completion.label().to_string(), label_style.clone())
- .with_highlights(mat.positions.clone())
+ Text::new(completion.label.text.clone(), settings.style.text.clone())
+ .with_soft_wrap(false)
+ .with_highlights(combine_syntax_and_fuzzy_match_highlights(
+ &completion.label.text,
+ settings.style.text.color.into(),
+ completion.label.runs.iter().filter_map(
+ |(range, highlight_id)| {
+ highlight_id
+ .style(&settings.style.syntax)
+ .map(|style| (range.clone(), style))
+ },
+ ),
+ &mat.positions,
+ ))
.contained()
.with_style(item_style)
.boxed(),
@@ -1742,7 +1745,11 @@ impl Editor {
.iter()
.enumerate()
.max_by_key(|(_, mat)| {
- state.completions[mat.candidate_id].label().chars().count()
+ state.completions[mat.candidate_id]
+ .label
+ .text
+ .chars()
+ .count()
})
.map(|(ix, _)| ix),
)
@@ -4699,6 +4706,77 @@ pub fn settings_builder(
})
}
+pub fn combine_syntax_and_fuzzy_match_highlights(
+ text: &str,
+ default_style: HighlightStyle,
+ syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
+ match_indices: &[usize],
+) -> Vec<(Range<usize>, HighlightStyle)> {
+ let mut result = Vec::new();
+ let mut match_indices = match_indices.iter().copied().peekable();
+
+ for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
+ {
+ syntax_highlight.font_properties.weight(Default::default());
+
+ // Add highlights for any fuzzy match characters before the next
+ // syntax highlight range.
+ while let Some(&match_index) = match_indices.peek() {
+ if match_index >= range.start {
+ break;
+ }
+ match_indices.next();
+ let end_index = char_ix_after(match_index, text);
+ let mut match_style = default_style;
+ match_style.font_properties.weight(fonts::Weight::BOLD);
+ result.push((match_index..end_index, match_style));
+ }
+
+ if range.start == usize::MAX {
+ break;
+ }
+
+ // Add highlights for any fuzzy match characters within the
+ // syntax highlight range.
+ let mut offset = range.start;
+ while let Some(&match_index) = match_indices.peek() {
+ if match_index >= range.end {
+ break;
+ }
+
+ match_indices.next();
+ if match_index > offset {
+ result.push((offset..match_index, syntax_highlight));
+ }
+
+ let mut end_index = char_ix_after(match_index, text);
+ while let Some(&next_match_index) = match_indices.peek() {
+ if next_match_index == end_index && next_match_index < range.end {
+ end_index = char_ix_after(next_match_index, text);
+ match_indices.next();
+ } else {
+ break;
+ }
+ }
+
+ let mut match_style = syntax_highlight;
+ match_style.font_properties.weight(fonts::Weight::BOLD);
+ result.push((match_index..end_index, match_style));
+ offset = end_index;
+ }
+
+ if offset < range.end {
+ result.push((offset..range.end, syntax_highlight));
+ }
+ }
+
+ fn char_ix_after(ix: usize, text: &str) -> usize {
+ ix + text[ix..].chars().next().unwrap().len_utf8()
+ }
+
+ result
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -7327,6 +7405,76 @@ mod tests {
});
}
+ #[test]
+ fn test_combine_syntax_and_fuzzy_match_highlights() {
+ let string = "abcdefghijklmnop";
+ let default = HighlightStyle::default();
+ let syntax_ranges = [
+ (
+ 0..3,
+ HighlightStyle {
+ color: Color::red(),
+ ..default
+ },
+ ),
+ (
+ 4..8,
+ HighlightStyle {
+ color: Color::green(),
+ ..default
+ },
+ ),
+ ];
+ let match_indices = [4, 6, 7, 8];
+ assert_eq!(
+ combine_syntax_and_fuzzy_match_highlights(
+ &string,
+ default,
+ syntax_ranges.into_iter(),
+ &match_indices,
+ ),
+ &[
+ (
+ 0..3,
+ HighlightStyle {
+ color: Color::red(),
+ ..default
+ },
+ ),
+ (
+ 4..5,
+ HighlightStyle {
+ color: Color::green(),
+ font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
+ ..default
+ },
+ ),
+ (
+ 5..6,
+ HighlightStyle {
+ color: Color::green(),
+ ..default
+ },
+ ),
+ (
+ 6..8,
+ HighlightStyle {
+ color: Color::green(),
+ font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
+ ..default
+ },
+ ),
+ (
+ 8..9,
+ HighlightStyle {
+ font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
+ ..default
+ },
+ ),
+ ]
+ );
+ }
+
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(row as u32, column as u32);
point..point
@@ -7,7 +7,7 @@ pub use crate::{
use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
outline::OutlineItem,
- range_from_lsp, Outline, ToLspPosition,
+ range_from_lsp, CompletionLabel, Outline, ToLspPosition,
};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
@@ -114,7 +114,7 @@ pub struct Diagnostic {
pub struct Completion<T> {
pub old_range: Range<T>,
pub new_text: String,
- pub label: Option<String>,
+ pub label: CompletionLabel,
pub lsp_completion: lsp::CompletionItem,
}
@@ -1829,7 +1829,7 @@ impl Buffer {
Some(Completion {
old_range: this.anchor_before(old_range.start)..this.anchor_after(old_range.end),
new_text,
- label: language.as_ref().and_then(|l| l.label_for_completion(&lsp_completion)),
+ label: language.as_ref().and_then(|l| l.label_for_completion(&lsp_completion)).unwrap_or_else(|| CompletionLabel::plain(&lsp_completion)),
lsp_completion,
})
} else {
@@ -2664,28 +2664,12 @@ impl Default for Diagnostic {
}
impl<T> Completion<T> {
- pub fn label(&self) -> &str {
- self.label.as_deref().unwrap_or(&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()])
+ (kind_key, &self.label.text[self.label.filter_range.clone()])
}
pub fn is_snippet(&self) -> bool {
@@ -5,7 +5,7 @@ use theme::SyntaxTheme;
#[derive(Clone, Debug)]
pub struct HighlightMap(Arc<[HighlightId]>);
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HighlightId(pub u32);
const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
@@ -49,11 +49,23 @@ pub trait ToLspPosition {
pub trait LspPostProcessor: 'static + Send + Sync {
fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
- fn label_for_completion(&self, _completion: &lsp::CompletionItem) -> Option<String> {
+ fn label_for_completion(
+ &self,
+ _: &lsp::CompletionItem,
+ _: &Language,
+ ) -> Option<CompletionLabel> {
None
}
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CompletionLabel {
+ pub text: String,
+ pub runs: Vec<(Range<usize>, HighlightId)>,
+ pub filter_range: Range<usize>,
+ pub left_aligned_len: usize,
+}
+
#[derive(Default, Deserialize)]
pub struct LanguageConfig {
pub name: String,
@@ -253,24 +265,26 @@ impl Language {
}
}
- pub fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option<String> {
+ pub fn label_for_completion(
+ &self,
+ completion: &lsp::CompletionItem,
+ ) -> Option<CompletionLabel> {
self.lsp_post_processor
- .as_ref()
- .and_then(|p| p.label_for_completion(completion))
+ .as_ref()?
+ .label_for_completion(completion, self)
}
- pub fn highlight_text<'a>(&'a self, text: &'a Rope) -> Vec<(Range<usize>, HighlightId)> {
+ pub fn highlight_text<'a>(
+ &'a self,
+ text: &'a Rope,
+ range: Range<usize>,
+ ) -> Vec<(Range<usize>, HighlightId)> {
let mut result = Vec::new();
if let Some(grammar) = &self.grammar {
let tree = grammar.parse_text(text, None);
let mut offset = 0;
- for chunk in BufferChunks::new(
- text,
- 0..text.len(),
- Some(&tree),
- self.grammar.as_ref(),
- vec![],
- ) {
+ for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![])
+ {
let end_offset = offset + chunk.text.len();
if let Some(highlight_id) = chunk.highlight_id {
result.push((offset..end_offset, highlight_id));
@@ -291,6 +305,10 @@ impl Language {
HighlightMap::new(grammar.highlights_query.capture_names(), theme);
}
}
+
+ pub fn grammar(&self) -> Option<&Arc<Grammar>> {
+ self.grammar.as_ref()
+ }
}
impl Grammar {
@@ -316,6 +334,28 @@ impl Grammar {
pub fn highlight_map(&self) -> HighlightMap {
self.highlight_map.lock().clone()
}
+
+ pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
+ let capture_id = self.highlights_query.capture_index_for_name(name)?;
+ Some(self.highlight_map.lock().get(capture_id))
+ }
+}
+
+impl CompletionLabel {
+ fn plain(completion: &lsp::CompletionItem) -> Self {
+ let mut result = Self {
+ text: completion.label.clone(),
+ runs: Vec::new(),
+ left_aligned_len: completion.label.len(),
+ filter_range: 0..completion.label.len(),
+ };
+ if let Some(filter_text) = &completion.filter_text {
+ if let Some(ix) = completion.label.find(filter_text) {
+ result.filter_range = ix..ix + filter_text.len();
+ }
+ }
+ result
+ }
}
#[cfg(any(test, feature = "test-support"))]
@@ -1,4 +1,6 @@
-use crate::{diagnostic_set::DiagnosticEntry, Completion, Diagnostic, Language, Operation};
+use crate::{
+ diagnostic_set::DiagnosticEntry, Completion, CompletionLabel, Diagnostic, Language, Operation,
+};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use collections::HashSet;
@@ -403,7 +405,9 @@ pub fn deserialize_completion(
Ok(Completion {
old_range: old_start..old_end,
new_text: completion.new_text,
- label: language.and_then(|l| l.label_for_completion(&lsp_completion)),
+ label: language
+ .and_then(|l| l.label_for_completion(&lsp_completion))
+ .unwrap_or(CompletionLabel::plain(&lsp_completion)),
lsp_completion,
})
}
@@ -1,12 +1,11 @@
use editor::{
- display_map::ToDisplayPoint, Anchor, AnchorRangeExt, Autoscroll, DisplayPoint, Editor,
- EditorSettings, ToPoint,
+ combine_syntax_and_fuzzy_match_highlights, display_map::ToDisplayPoint, Anchor, AnchorRangeExt,
+ Autoscroll, DisplayPoint, Editor, EditorSettings, ToPoint,
};
use fuzzy::StringMatch;
use gpui::{
action,
elements::*,
- fonts::{self, HighlightStyle},
geometry::vector::Vector2F,
keymap::{self, Binding},
AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
@@ -17,7 +16,6 @@ use ordered_float::OrderedFloat;
use postage::watch;
use std::{
cmp::{self, Reverse},
- ops::Range,
sync::Arc,
};
use workspace::{
@@ -362,7 +360,7 @@ impl OutlineView {
.with_highlights(combine_syntax_and_fuzzy_match_highlights(
&outline_item.text,
style.label.text.clone().into(),
- &outline_item.highlight_ranges,
+ outline_item.highlight_ranges.iter().cloned(),
&string_match.positions,
))
.contained()
@@ -372,153 +370,3 @@ impl OutlineView {
.boxed()
}
}
-
-fn combine_syntax_and_fuzzy_match_highlights(
- text: &str,
- default_style: HighlightStyle,
- syntax_ranges: &[(Range<usize>, HighlightStyle)],
- match_indices: &[usize],
-) -> Vec<(Range<usize>, HighlightStyle)> {
- let mut result = Vec::new();
- let mut match_indices = match_indices.iter().copied().peekable();
-
- for (range, mut syntax_highlight) in syntax_ranges
- .iter()
- .cloned()
- .chain([(usize::MAX..0, Default::default())])
- {
- syntax_highlight.font_properties.weight(Default::default());
-
- // Add highlights for any fuzzy match characters before the next
- // syntax highlight range.
- while let Some(&match_index) = match_indices.peek() {
- if match_index >= range.start {
- break;
- }
- match_indices.next();
- let end_index = char_ix_after(match_index, text);
- let mut match_style = default_style;
- match_style.font_properties.weight(fonts::Weight::BOLD);
- result.push((match_index..end_index, match_style));
- }
-
- if range.start == usize::MAX {
- break;
- }
-
- // Add highlights for any fuzzy match characters within the
- // syntax highlight range.
- let mut offset = range.start;
- while let Some(&match_index) = match_indices.peek() {
- if match_index >= range.end {
- break;
- }
-
- match_indices.next();
- if match_index > offset {
- result.push((offset..match_index, syntax_highlight));
- }
-
- let mut end_index = char_ix_after(match_index, text);
- while let Some(&next_match_index) = match_indices.peek() {
- if next_match_index == end_index && next_match_index < range.end {
- end_index = char_ix_after(next_match_index, text);
- match_indices.next();
- } else {
- break;
- }
- }
-
- let mut match_style = syntax_highlight;
- match_style.font_properties.weight(fonts::Weight::BOLD);
- result.push((match_index..end_index, match_style));
- offset = end_index;
- }
-
- if offset < range.end {
- result.push((offset..range.end, syntax_highlight));
- }
- }
-
- result
-}
-
-fn char_ix_after(ix: usize, text: &str) -> usize {
- ix + text[ix..].chars().next().unwrap().len_utf8()
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use gpui::{color::Color, fonts::HighlightStyle};
-
- #[test]
- fn test_combine_syntax_and_fuzzy_match_highlights() {
- let string = "abcdefghijklmnop";
- let default = HighlightStyle::default();
- let syntax_ranges = [
- (
- 0..3,
- HighlightStyle {
- color: Color::red(),
- ..default
- },
- ),
- (
- 4..8,
- HighlightStyle {
- color: Color::green(),
- ..default
- },
- ),
- ];
- let match_indices = [4, 6, 7, 8];
- assert_eq!(
- combine_syntax_and_fuzzy_match_highlights(
- &string,
- default,
- &syntax_ranges,
- &match_indices,
- ),
- &[
- (
- 0..3,
- HighlightStyle {
- color: Color::red(),
- ..default
- },
- ),
- (
- 4..5,
- HighlightStyle {
- color: Color::green(),
- font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
- ..default
- },
- ),
- (
- 5..6,
- HighlightStyle {
- color: Color::green(),
- ..default
- },
- ),
- (
- 6..8,
- HighlightStyle {
- color: Color::green(),
- font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
- ..default
- },
- ),
- (
- 8..9,
- HighlightStyle {
- font_properties: *fonts::Properties::default().weight(fonts::Weight::BOLD),
- ..default
- },
- ),
- ]
- );
- }
-}
@@ -32,18 +32,36 @@ impl LspPostProcessor for RustPostProcessor {
}
}
- fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option<String> {
+ fn label_for_completion(
+ &self,
+ completion: &lsp::CompletionItem,
+ language: &Language,
+ ) -> Option<CompletionLabel> {
let detail = completion.detail.as_ref()?;
match completion.kind {
- Some(
- lsp::CompletionItemKind::CONSTANT
- | lsp::CompletionItemKind::FIELD
- | lsp::CompletionItemKind::VARIABLE,
- ) => {
- let mut label = completion.label.clone();
- label.push_str(": ");
- label.push_str(detail);
- Some(label)
+ Some(lsp::CompletionItemKind::FIELD) => {
+ let name = &completion.label;
+ let text = format!("{}: {}", name, detail);
+ let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
+ let runs = language.highlight_text(&source, 11..11 + text.len());
+ return Some(CompletionLabel {
+ text,
+ runs,
+ filter_range: 0..name.len(),
+ left_aligned_len: name.len(),
+ });
+ }
+ Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) => {
+ let name = &completion.label;
+ let text = format!("{}: {}", name, detail);
+ let source = Rope::from(format!("let {} = ();", text).as_str());
+ let runs = language.highlight_text(&source, 4..4 + text.len());
+ return Some(CompletionLabel {
+ text,
+ runs,
+ filter_range: 0..name.len(),
+ left_aligned_len: name.len(),
+ });
}
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) => {
lazy_static! {
@@ -51,13 +69,20 @@ impl LspPostProcessor for RustPostProcessor {
}
if detail.starts_with("fn(") {
- Some(REGEX.replace(&completion.label, &detail[2..]).to_string())
- } else {
- None
+ let text = REGEX.replace(&completion.label, &detail[2..]).to_string();
+ let source = Rope::from(format!("fn {} {{}}", text).as_str());
+ let runs = language.highlight_text(&source, 3..3 + text.len());
+ return Some(CompletionLabel {
+ left_aligned_len: text.find("->").unwrap_or(text.len()),
+ filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
+ text,
+ runs,
+ });
}
}
- _ => None,
+ _ => {}
}
+ None
}
}
@@ -100,9 +125,10 @@ fn load_query(path: &str) -> Cow<'static, str> {
#[cfg(test)]
mod tests {
+ use super::*;
+ use gpui::color::Color;
use language::LspPostProcessor;
-
- use super::RustPostProcessor;
+ use theme::SyntaxTheme;
#[test]
fn test_process_rust_diagnostics() {
@@ -144,4 +170,82 @@ mod tests {
"cannot borrow `self.d` as mutable\n`self` is a `&` reference"
);
}
+
+ #[test]
+ fn test_process_rust_completions() {
+ let language = rust();
+ let grammar = language.grammar().unwrap();
+ let theme = SyntaxTheme::new(vec![
+ ("type".into(), Color::green().into()),
+ ("keyword".into(), Color::blue().into()),
+ ("function".into(), Color::red().into()),
+ ("property".into(), Color::white().into()),
+ ]);
+
+ language.set_theme(&theme);
+
+ let highlight_function = grammar.highlight_id_for_name("function").unwrap();
+ let highlight_type = grammar.highlight_id_for_name("type").unwrap();
+ let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
+ let highlight_field = grammar.highlight_id_for_name("property").unwrap();
+
+ assert_eq!(
+ language.label_for_completion(&lsp::CompletionItem {
+ kind: Some(lsp::CompletionItemKind::FUNCTION),
+ label: "hello(…)".to_string(),
+ detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
+ ..Default::default()
+ }),
+ Some(CompletionLabel {
+ text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+ filter_range: 0..5,
+ runs: vec![
+ (0..5, highlight_function),
+ (7..10, highlight_keyword),
+ (11..17, highlight_type),
+ (18..19, highlight_type),
+ (25..28, highlight_type),
+ (29..30, highlight_type),
+ ],
+ left_aligned_len: 22,
+ })
+ );
+
+ assert_eq!(
+ language.label_for_completion(&lsp::CompletionItem {
+ kind: Some(lsp::CompletionItemKind::FIELD),
+ label: "len".to_string(),
+ detail: Some("usize".to_string()),
+ ..Default::default()
+ }),
+ Some(CompletionLabel {
+ text: "len: usize".to_string(),
+ filter_range: 0..3,
+ runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
+ left_aligned_len: 3,
+ })
+ );
+
+ assert_eq!(
+ language.label_for_completion(&lsp::CompletionItem {
+ kind: Some(lsp::CompletionItemKind::FUNCTION),
+ label: "hello(…)".to_string(),
+ detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
+ ..Default::default()
+ }),
+ Some(CompletionLabel {
+ text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+ filter_range: 0..5,
+ runs: vec![
+ (0..5, highlight_function),
+ (7..10, highlight_keyword),
+ (11..17, highlight_type),
+ (18..19, highlight_type),
+ (25..28, highlight_type),
+ (29..30, highlight_type),
+ ],
+ left_aligned_len: 22,
+ })
+ );
+ }
}