mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-30 22:34:13 +00:00
Extract a gpui::combine_highlights
function
This commit is contained in:
parent
e5b6b0ee9e
commit
d31b53b912
4 changed files with 185 additions and 143 deletions
|
@ -1276,11 +1276,16 @@ impl CompletionsMenu {
|
||||||
&None
|
&None
|
||||||
};
|
};
|
||||||
|
|
||||||
let highlights = combine_syntax_and_fuzzy_match_highlights(
|
let highlights = gpui::combine_highlights(
|
||||||
&completion.label.text,
|
mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
|
||||||
&style.text,
|
styled_runs_for_code_label(&completion.label, &style.syntax).map(
|
||||||
styled_runs_for_code_label(&completion.label, &style.syntax),
|
|(range, mut highlight)| {
|
||||||
&mat.positions,
|
// Ignore font weight for syntax highlighting, as we'll use it
|
||||||
|
// for fuzzy matches.
|
||||||
|
highlight.font_weight = None;
|
||||||
|
(range, highlight)
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let completion_label = StyledText::new(completion.label.text.clone())
|
let completion_label = StyledText::new(completion.label.text.clone())
|
||||||
.with_runs(text_runs_for_highlights(
|
.with_runs(text_runs_for_highlights(
|
||||||
|
@ -10056,75 +10061,6 @@ pub fn text_runs_for_highlights(
|
||||||
runs
|
runs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn combine_syntax_and_fuzzy_match_highlights(
|
|
||||||
text: &str,
|
|
||||||
default_style: &TextStyle,
|
|
||||||
syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
|
||||||
match_indices: &[usize],
|
|
||||||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
|
||||||
let mut highlights = 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_weight = None;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
highlights.push((match_index..end_index, FontWeight::BOLD.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
highlights.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_weight = Some(FontWeight::BOLD);
|
|
||||||
highlights.push((match_index..end_index, match_style));
|
|
||||||
offset = end_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset < range.end {
|
|
||||||
highlights.push((offset..range.end, syntax_highlight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn char_ix_after(ix: usize, text: &str) -> usize {
|
|
||||||
ix + text[ix..].chars().next().unwrap().len_utf8()
|
|
||||||
}
|
|
||||||
|
|
||||||
highlights
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn styled_runs_for_code_label<'a>(
|
pub fn styled_runs_for_code_label<'a>(
|
||||||
label: &'a CodeLabel,
|
label: &'a CodeLabel,
|
||||||
syntax_theme: &'a theme::SyntaxTheme,
|
syntax_theme: &'a theme::SyntaxTheme,
|
||||||
|
|
|
@ -6740,75 +6740,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_combine_syntax_and_fuzzy_match_highlights() {
|
|
||||||
let string = "abcdefghijklmnop";
|
|
||||||
let syntax_ranges = [
|
|
||||||
(
|
|
||||||
0..3,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::red()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
4..8,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::green()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
];
|
|
||||||
let match_indices = [4, 6, 7, 8];
|
|
||||||
assert_eq!(
|
|
||||||
combine_syntax_and_fuzzy_match_highlights(
|
|
||||||
string,
|
|
||||||
&TextStyle::default(),
|
|
||||||
syntax_ranges.into_iter(),
|
|
||||||
&match_indices,
|
|
||||||
),
|
|
||||||
&[
|
|
||||||
(
|
|
||||||
0..3,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::red()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
4..5,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::green()),
|
|
||||||
font_weight: Some(gpui::FontWeight::BOLD),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
5..6,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::green()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
6..8,
|
|
||||||
HighlightStyle {
|
|
||||||
color: Some(Hsla::green()),
|
|
||||||
font_weight: Some(gpui::FontWeight::BOLD),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
8..9,
|
|
||||||
HighlightStyle {
|
|
||||||
font_weight: Some(gpui::FontWeight::BOLD),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn go_to_prev_overlapping_diagnostic(
|
async fn go_to_prev_overlapping_diagnostic(
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
|
|
|
@ -6,6 +6,8 @@ use gpui::BackgroundExecutor;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
iter,
|
||||||
|
ops::Range,
|
||||||
sync::atomic::AtomicBool,
|
sync::atomic::AtomicBool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,6 +56,30 @@ pub struct StringMatch {
|
||||||
pub string: String,
|
pub string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StringMatch {
|
||||||
|
pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
|
||||||
|
let mut positions = self.positions.iter().peekable();
|
||||||
|
iter::from_fn(move || {
|
||||||
|
while let Some(start) = positions.next().copied() {
|
||||||
|
let mut end = start + self.char_len_at_index(start);
|
||||||
|
while let Some(next_start) = positions.peek() {
|
||||||
|
if end == **next_start {
|
||||||
|
end += self.char_len_at_index(end);
|
||||||
|
positions.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(start..end);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char_len_at_index(&self, ix: usize) -> usize {
|
||||||
|
self.string[ix..].chars().next().unwrap().len_utf8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for StringMatch {
|
impl PartialEq for StringMatch {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.cmp(other).is_eq()
|
self.cmp(other).is_eq()
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
use std::{iter, mem, ops::Range};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||||
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
|
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
|
||||||
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
||||||
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
|
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
|
||||||
};
|
};
|
||||||
|
use collections::HashSet;
|
||||||
use refineable::{Cascade, Refineable};
|
use refineable::{Cascade, Refineable};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
pub use taffy::style::{
|
pub use taffy::style::{
|
||||||
|
@ -512,6 +515,15 @@ impl From<FontWeight> for HighlightStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FontStyle> for HighlightStyle {
|
||||||
|
fn from(font_style: FontStyle) -> Self {
|
||||||
|
Self {
|
||||||
|
font_style: Some(font_style),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Rgba> for HighlightStyle {
|
impl From<Rgba> for HighlightStyle {
|
||||||
fn from(color: Rgba) -> Self {
|
fn from(color: Rgba) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -520,3 +532,140 @@ impl From<Rgba> for HighlightStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn combine_highlights(
|
||||||
|
a: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||||
|
b: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||||
|
) -> impl Iterator<Item = (Range<usize>, HighlightStyle)> {
|
||||||
|
let mut endpoints = Vec::new();
|
||||||
|
let mut highlights = Vec::new();
|
||||||
|
for (range, highlight) in a.into_iter().chain(b) {
|
||||||
|
if !range.is_empty() {
|
||||||
|
let highlight_id = highlights.len();
|
||||||
|
endpoints.push((range.start, highlight_id, true));
|
||||||
|
endpoints.push((range.end, highlight_id, false));
|
||||||
|
highlights.push(highlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endpoints.sort_unstable_by_key(|(position, _, _)| *position);
|
||||||
|
let mut endpoints = endpoints.into_iter().peekable();
|
||||||
|
|
||||||
|
let mut active_styles = HashSet::default();
|
||||||
|
let mut ix = 0;
|
||||||
|
iter::from_fn(move || {
|
||||||
|
while let Some((endpoint_ix, highlight_id, is_start)) = endpoints.peek() {
|
||||||
|
let prev_index = mem::replace(&mut ix, *endpoint_ix);
|
||||||
|
if ix > prev_index && !active_styles.is_empty() {
|
||||||
|
let mut current_style = HighlightStyle::default();
|
||||||
|
for highlight_id in &active_styles {
|
||||||
|
current_style.highlight(highlights[*highlight_id]);
|
||||||
|
}
|
||||||
|
return Some((prev_index..ix, current_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
if *is_start {
|
||||||
|
active_styles.insert(*highlight_id);
|
||||||
|
} else {
|
||||||
|
active_styles.remove(highlight_id);
|
||||||
|
}
|
||||||
|
endpoints.next();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{blue, green, red, yellow};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_combine_highlights() {
|
||||||
|
assert_eq!(
|
||||||
|
combine_highlights(
|
||||||
|
[
|
||||||
|
(0..5, green().into()),
|
||||||
|
(4..10, FontWeight::BOLD.into()),
|
||||||
|
(15..20, yellow().into()),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(2..6, FontStyle::Italic.into()),
|
||||||
|
(1..3, blue().into()),
|
||||||
|
(21..23, red().into()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
0..1,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(green()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1..2,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(blue()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2..3,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(blue()),
|
||||||
|
font_style: Some(FontStyle::Italic),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3..4,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(green()),
|
||||||
|
font_style: Some(FontStyle::Italic),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
4..5,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(green()),
|
||||||
|
font_weight: Some(FontWeight::BOLD),
|
||||||
|
font_style: Some(FontStyle::Italic),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
5..6,
|
||||||
|
HighlightStyle {
|
||||||
|
font_weight: Some(FontWeight::BOLD),
|
||||||
|
font_style: Some(FontStyle::Italic),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
6..10,
|
||||||
|
HighlightStyle {
|
||||||
|
font_weight: Some(FontWeight::BOLD),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
15..20,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(yellow()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
21..23,
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(red()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue