Extract a gpui::combine_highlights function

This commit is contained in:
Antonio Scandurra 2023-11-24 16:31:38 +01:00
parent e5b6b0ee9e
commit d31b53b912
4 changed files with 185 additions and 143 deletions

View File

@ -1276,11 +1276,16 @@ impl CompletionsMenu {
&None
};
let highlights = combine_syntax_and_fuzzy_match_highlights(
&completion.label.text,
&style.text,
styled_runs_for_code_label(&completion.label, &style.syntax),
&mat.positions,
let highlights = gpui::combine_highlights(
mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
styled_runs_for_code_label(&completion.label, &style.syntax).map(
|(range, mut highlight)| {
// 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())
.with_runs(text_runs_for_highlights(
@ -10056,75 +10061,6 @@ pub fn text_runs_for_highlights(
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>(
label: &'a CodeLabel,
syntax_theme: &'a theme::SyntaxTheme,

View File

@ -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]
async fn go_to_prev_overlapping_diagnostic(
executor: BackgroundExecutor,

View File

@ -6,6 +6,8 @@ use gpui::BackgroundExecutor;
use std::{
borrow::Cow,
cmp::{self, Ordering},
iter,
ops::Range,
sync::atomic::AtomicBool,
};
@ -54,6 +56,30 @@ pub struct StringMatch {
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 {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()

View File

@ -1,9 +1,12 @@
use std::{iter, mem, ops::Range};
use crate::{
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
};
use collections::HashSet;
use refineable::{Cascade, Refineable};
use smallvec::SmallVec;
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 {
fn from(color: Rgba) -> 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()
}
)
]
);
}
}