Start work on syntax highlighting completions
This commit is contained in:
parent
45898daf83
commit
439d12cb85
7 changed files with 349 additions and 221 deletions
|
@ -20,7 +20,7 @@ use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::*,
|
elements::*,
|
||||||
executor,
|
executor,
|
||||||
fonts::TextStyle,
|
fonts::{self, HighlightStyle, TextStyle},
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
keymap::Binding,
|
keymap::Binding,
|
||||||
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
|
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
|
||||||
|
@ -489,7 +489,7 @@ impl CompletionState {
|
||||||
});
|
});
|
||||||
|
|
||||||
for mat in &mut matches {
|
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 {
|
for position in &mut mat.positions {
|
||||||
*position += filter_start;
|
*position += filter_start;
|
||||||
}
|
}
|
||||||
|
@ -1628,7 +1628,7 @@ impl Editor {
|
||||||
.map(|(id, completion)| {
|
.map(|(id, completion)| {
|
||||||
StringMatchCandidate::new(
|
StringMatchCandidate::new(
|
||||||
id,
|
id,
|
||||||
completion.lsp_completion.label[completion.filter_range()].into(),
|
completion.label.text[completion.label.filter_range.clone()].into(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -1710,15 +1710,6 @@ impl Editor {
|
||||||
move |range, items, cx| {
|
move |range, items, cx| {
|
||||||
let settings = build_settings(cx);
|
let settings = build_settings(cx);
|
||||||
let start_ix = range.start;
|
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() {
|
for (ix, mat) in matches[range].iter().enumerate() {
|
||||||
let item_style = if start_ix + ix == selected_item {
|
let item_style = if start_ix + ix == selected_item {
|
||||||
settings.style.autocomplete.selected_item
|
settings.style.autocomplete.selected_item
|
||||||
|
@ -1727,8 +1718,20 @@ impl Editor {
|
||||||
};
|
};
|
||||||
let completion = &completions[mat.candidate_id];
|
let completion = &completions[mat.candidate_id];
|
||||||
items.push(
|
items.push(
|
||||||
Label::new(completion.label().to_string(), label_style.clone())
|
Text::new(completion.label.text.clone(), settings.style.text.clone())
|
||||||
.with_highlights(mat.positions.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()
|
.contained()
|
||||||
.with_style(item_style)
|
.with_style(item_style)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
|
@ -1742,7 +1745,11 @@ impl Editor {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.max_by_key(|(_, mat)| {
|
.max_by_key(|(_, mat)| {
|
||||||
state.completions[mat.candidate_id].label().chars().count()
|
state.completions[mat.candidate_id]
|
||||||
|
.label
|
||||||
|
.text
|
||||||
|
.chars()
|
||||||
|
.count()
|
||||||
})
|
})
|
||||||
.map(|(ix, _)| ix),
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(row as u32, column as u32);
|
let point = DisplayPoint::new(row as u32, column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub use crate::{
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||||
outline::OutlineItem,
|
outline::OutlineItem,
|
||||||
range_from_lsp, Outline, ToLspPosition,
|
range_from_lsp, CompletionLabel, Outline, ToLspPosition,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
@ -114,7 +114,7 @@ pub struct Diagnostic {
|
||||||
pub struct Completion<T> {
|
pub struct Completion<T> {
|
||||||
pub old_range: Range<T>,
|
pub old_range: Range<T>,
|
||||||
pub new_text: String,
|
pub new_text: String,
|
||||||
pub label: Option<String>,
|
pub label: CompletionLabel,
|
||||||
pub lsp_completion: lsp::CompletionItem,
|
pub lsp_completion: lsp::CompletionItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1829,7 +1829,7 @@ impl Buffer {
|
||||||
Some(Completion {
|
Some(Completion {
|
||||||
old_range: this.anchor_before(old_range.start)..this.anchor_after(old_range.end),
|
old_range: this.anchor_before(old_range.start)..this.anchor_after(old_range.end),
|
||||||
new_text,
|
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,
|
lsp_completion,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -2664,28 +2664,12 @@ impl Default for Diagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Completion<T> {
|
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) {
|
pub fn sort_key(&self) -> (usize, &str) {
|
||||||
let kind_key = match self.lsp_completion.kind {
|
let kind_key = match self.lsp_completion.kind {
|
||||||
Some(lsp::CompletionItemKind::VARIABLE) => 0,
|
Some(lsp::CompletionItemKind::VARIABLE) => 0,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
};
|
};
|
||||||
(kind_key, &self.label()[self.filter_range()])
|
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_snippet(&self) -> bool {
|
pub fn is_snippet(&self) -> bool {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use theme::SyntaxTheme;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HighlightMap(Arc<[HighlightId]>);
|
pub struct HighlightMap(Arc<[HighlightId]>);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct HighlightId(pub u32);
|
pub struct HighlightId(pub u32);
|
||||||
|
|
||||||
const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
|
const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
|
||||||
|
|
|
@ -49,11 +49,23 @@ pub trait ToLspPosition {
|
||||||
|
|
||||||
pub trait LspPostProcessor: 'static + Send + Sync {
|
pub trait LspPostProcessor: 'static + Send + Sync {
|
||||||
fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
|
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
|
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)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct LanguageConfig {
|
pub struct LanguageConfig {
|
||||||
pub name: String,
|
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
|
self.lsp_post_processor
|
||||||
.as_ref()
|
.as_ref()?
|
||||||
.and_then(|p| p.label_for_completion(completion))
|
.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();
|
let mut result = Vec::new();
|
||||||
if let Some(grammar) = &self.grammar {
|
if let Some(grammar) = &self.grammar {
|
||||||
let tree = grammar.parse_text(text, None);
|
let tree = grammar.parse_text(text, None);
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
for chunk in BufferChunks::new(
|
for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![])
|
||||||
text,
|
{
|
||||||
0..text.len(),
|
|
||||||
Some(&tree),
|
|
||||||
self.grammar.as_ref(),
|
|
||||||
vec![],
|
|
||||||
) {
|
|
||||||
let end_offset = offset + chunk.text.len();
|
let end_offset = offset + chunk.text.len();
|
||||||
if let Some(highlight_id) = chunk.highlight_id {
|
if let Some(highlight_id) = chunk.highlight_id {
|
||||||
result.push((offset..end_offset, highlight_id));
|
result.push((offset..end_offset, highlight_id));
|
||||||
|
@ -291,6 +305,10 @@ impl Language {
|
||||||
HighlightMap::new(grammar.highlights_query.capture_names(), theme);
|
HighlightMap::new(grammar.highlights_query.capture_names(), theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn grammar(&self) -> Option<&Arc<Grammar>> {
|
||||||
|
self.grammar.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Grammar {
|
impl Grammar {
|
||||||
|
@ -316,6 +334,28 @@ impl Grammar {
|
||||||
pub fn highlight_map(&self) -> HighlightMap {
|
pub fn highlight_map(&self) -> HighlightMap {
|
||||||
self.highlight_map.lock().clone()
|
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"))]
|
#[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 anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
|
@ -403,7 +405,9 @@ pub fn deserialize_completion(
|
||||||
Ok(Completion {
|
Ok(Completion {
|
||||||
old_range: old_start..old_end,
|
old_range: old_start..old_end,
|
||||||
new_text: completion.new_text,
|
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,
|
lsp_completion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, Autoscroll, DisplayPoint, Editor,
|
combine_syntax_and_fuzzy_match_highlights, display_map::ToDisplayPoint, Anchor, AnchorRangeExt,
|
||||||
EditorSettings, ToPoint,
|
Autoscroll, DisplayPoint, Editor, EditorSettings, ToPoint,
|
||||||
};
|
};
|
||||||
use fuzzy::StringMatch;
|
use fuzzy::StringMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
fonts::{self, HighlightStyle},
|
|
||||||
geometry::vector::Vector2F,
|
geometry::vector::Vector2F,
|
||||||
keymap::{self, Binding},
|
keymap::{self, Binding},
|
||||||
AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||||
|
@ -17,7 +16,6 @@ use ordered_float::OrderedFloat;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Reverse},
|
cmp::{self, Reverse},
|
||||||
ops::Range,
|
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -362,7 +360,7 @@ impl OutlineView {
|
||||||
.with_highlights(combine_syntax_and_fuzzy_match_highlights(
|
.with_highlights(combine_syntax_and_fuzzy_match_highlights(
|
||||||
&outline_item.text,
|
&outline_item.text,
|
||||||
style.label.text.clone().into(),
|
style.label.text.clone().into(),
|
||||||
&outline_item.highlight_ranges,
|
outline_item.highlight_ranges.iter().cloned(),
|
||||||
&string_match.positions,
|
&string_match.positions,
|
||||||
))
|
))
|
||||||
.contained()
|
.contained()
|
||||||
|
@ -372,153 +370,3 @@ impl OutlineView {
|
||||||
.boxed()
|
.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()?;
|
let detail = completion.detail.as_ref()?;
|
||||||
match completion.kind {
|
match completion.kind {
|
||||||
Some(
|
Some(lsp::CompletionItemKind::FIELD) => {
|
||||||
lsp::CompletionItemKind::CONSTANT
|
let name = &completion.label;
|
||||||
| lsp::CompletionItemKind::FIELD
|
let text = format!("{}: {}", name, detail);
|
||||||
| lsp::CompletionItemKind::VARIABLE,
|
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
|
||||||
) => {
|
let runs = language.highlight_text(&source, 11..11 + text.len());
|
||||||
let mut label = completion.label.clone();
|
return Some(CompletionLabel {
|
||||||
label.push_str(": ");
|
text,
|
||||||
label.push_str(detail);
|
runs,
|
||||||
Some(label)
|
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) => {
|
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) => {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -51,15 +69,22 @@ impl LspPostProcessor for RustPostProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if detail.starts_with("fn(") {
|
if detail.starts_with("fn(") {
|
||||||
Some(REGEX.replace(&completion.label, &detail[2..]).to_string())
|
let text = REGEX.replace(&completion.label, &detail[2..]).to_string();
|
||||||
} else {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_language_registry() -> LanguageRegistry {
|
pub fn build_language_registry() -> LanguageRegistry {
|
||||||
let mut languages = LanguageRegistry::default();
|
let mut languages = LanguageRegistry::default();
|
||||||
|
@ -100,9 +125,10 @@ fn load_query(path: &str) -> Cow<'static, str> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gpui::color::Color;
|
||||||
use language::LspPostProcessor;
|
use language::LspPostProcessor;
|
||||||
|
use theme::SyntaxTheme;
|
||||||
use super::RustPostProcessor;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_rust_diagnostics() {
|
fn test_process_rust_diagnostics() {
|
||||||
|
@ -144,4 +170,82 @@ mod tests {
|
||||||
"cannot borrow `self.d` as mutable\n`self` is a `&` reference"
|
"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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue