highlighter: Fix #1204 changes cause to incorrect highlight result. (#1218)

This PR for fix some incorrect changes in #1204 

## Before

<img width="854" height="864" alt="image"
src="https://github.com/user-attachments/assets/eebd07e5-3437-444d-a55d-2941228e3b08"
/>

## After

<img width="954" height="962" alt="image"
src="https://github.com/user-attachments/assets/60761848-8d4f-4fe9-88b9-f0b0f0985d2f"
/>
<img width="1515" height="867" alt="image"
src="https://github.com/user-attachments/assets/3d4654a7-f977-4919-9c9e-f013fcdd8fde"
/>
This commit is contained in:
Jason Lee 2025-09-08 16:14:07 +08:00 committed by GitHub
parent 78dccd4d79
commit a9e6b49a15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 50 deletions

View file

@ -49,11 +49,19 @@ impl Lang {
}
}
const LANGUAGES: [(Lang, &'static str); 10] = [
const LANGUAGES: [(Lang, &'static str); 12] = [
(
Lang::BuiltIn(Language::Rust),
include_str!("./fixtures/test.rs"),
),
(
Lang::BuiltIn(Language::Markdown),
include_str!("./fixtures/test.md"),
),
(
Lang::BuiltIn(Language::Html),
include_str!("./fixtures/test.html"),
),
(
Lang::BuiltIn(Language::JavaScript),
include_str!("./fixtures/test.js"),
@ -90,8 +98,17 @@ const LANGUAGES: [(Lang, &'static str); 10] = [
];
impl Example {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let default_language = LANGUAGES[0].clone();
pub fn new(default: Option<String>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let default_language = if let Some(name) = default {
LANGUAGES
.iter()
.find(|s| s.0.name().starts_with(name.trim()))
.cloned()
.unwrap_or(LANGUAGES[0].clone())
} else {
LANGUAGES[0].clone()
};
let editor = cx.new(|cx| {
InputState::new(window, cx)
.code_editor(default_language.0.name().to_string())
@ -147,10 +164,6 @@ impl Example {
}
}
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
fn set_markers(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.language.name() != "rust" {
return;
@ -310,11 +323,18 @@ impl Render for Example {
fn main() {
let app = Application::new().with_assets(Assets);
// Parse `cargo run -- <story_name>`
let name = std::env::args().nth(1);
app.run(move |cx| {
story::init(cx);
init(cx);
cx.activate(true);
story::create_new_window("Code Editor", Example::view, cx);
story::create_new_window(
"Code Editor",
|window, cx| cx.new(|cx| Example::new(name, window, cx)),
cx,
);
});
}

View file

@ -579,7 +579,6 @@ impl SyntaxHighlighter {
});
filter.next();
let mut last_range = start_offset..start_offset;
// let mut iter_count = 0;
while let Some(item) = filter.item() {
// iter_count += 1;
@ -592,18 +591,7 @@ impl SyntaxHighlighter {
node_range.end = node_range.start;
}
// Ensure every range is connected.
if last_range.end < node_range.start {
styles.push((last_range.end..node_range.start, HighlightStyle::default()));
}
let start = node_range.start.max(last_range.end);
styles.push((
start..node_range.end,
theme.style(name.as_ref()).unwrap_or_default(),
));
last_range = node_range;
styles.push((node_range, theme.style(name.as_ref()).unwrap_or_default()));
filter.next();
}
// dbg!(iter_count);
@ -613,12 +601,7 @@ impl SyntaxHighlighter {
return vec![(start_offset..range.end, HighlightStyle::default())];
}
// Ensure the last range is connected to the end of the line.
if last_range.end < range.end {
styles.push((last_range.end..range.end, HighlightStyle::default()));
}
let styles = unique_styles(styles);
let styles = unique_styles(&range, styles);
// NOTE: DO NOT remove this comment, it is used for debugging.
// for style in &styles {
@ -645,44 +628,58 @@ impl SyntaxHighlighter {
///
/// AABCCDDCEEEE
pub(crate) fn unique_styles(
total_range: &Range<usize>,
styles: Vec<(Range<usize>, HighlightStyle)>,
) -> Vec<(Range<usize>, HighlightStyle)> {
if styles.is_empty() {
return styles;
}
// Collect all boundary points and track which are "significant" (range endpoints)
let mut boundaries = BTreeSet::new();
let mut significant_boundaries = BTreeSet::new();
let mut intervals = BTreeSet::new();
let mut significant_intervals = BTreeSet::new();
// For example
//
// from: [(6..11), (6..11), (11..17), (17..25), (16..19), (25..59))]
// to: [6, 11, 16, 17, 19, 25, 59]
intervals.insert(total_range.start);
intervals.insert(total_range.end);
for (range, _) in &styles {
boundaries.insert(range.start);
boundaries.insert(range.end);
significant_boundaries.insert(range.end); // End points are significant for merging decisions
intervals.insert(range.start);
intervals.insert(range.end);
significant_intervals.insert(range.end); // End points are significant for merging decisions
}
let boundaries: Vec<usize> = boundaries.into_iter().collect();
let mut result = Vec::with_capacity(boundaries.len().saturating_sub(1));
let intervals: Vec<usize> = intervals.into_iter().collect();
let mut result = Vec::with_capacity(intervals.len().saturating_sub(1));
// For each interval between boundaries, find the top-most style
for i in 0..boundaries.len().saturating_sub(1) {
let interval_start = boundaries[i];
let interval_end = boundaries[i + 1];
if interval_start >= interval_end {
//
// Result e.g.:
//
// [(6..11, red), (11..16, green), (16..17, blue), (17..19, red), (19..25, clean), (25..59, blue)]
for i in 0..intervals.len().saturating_sub(1) {
let interval = intervals[i]..intervals[i + 1];
if interval.start >= interval.end {
continue;
}
// Find the last (top-most) style that covers this interval
let mut top_style: Option<&HighlightStyle> = None;
let mut top_style: Option<HighlightStyle> = None;
for (range, style) in &styles {
if range.start <= interval_start && interval_end <= range.end {
top_style = Some(style);
if range.start <= interval.start && interval.end <= range.end {
if let Some(top_style) = &mut top_style {
merge_highlight_style(top_style, style);
} else {
top_style = Some(*style);
}
}
}
if let Some(style) = top_style {
result.push((interval_start..interval_end, *style));
result.push((interval, style));
} else {
result.push((interval, HighlightStyle::default()));
}
}
@ -692,7 +689,7 @@ pub(crate) fn unique_styles(
if let Some((last_range, last_style)) = merged.last_mut() {
if last_range.end == range.start
&& *last_style == style
&& !significant_boundaries.contains(&range.start)
&& !significant_intervals.contains(&range.start)
{
// Merge adjacent ranges with same style, but not across significant boundaries
last_range.end = range.end;
@ -705,6 +702,31 @@ pub(crate) fn unique_styles(
merged
}
/// Merge other style (Other on top)
fn merge_highlight_style(style: &mut HighlightStyle, other: &HighlightStyle) {
if let Some(color) = other.color {
style.color = Some(color);
}
if let Some(font_weight) = other.font_weight {
style.font_weight = Some(font_weight);
}
if let Some(font_style) = other.font_style {
style.font_style = Some(font_style);
}
if let Some(background_color) = other.background_color {
style.background_color = Some(background_color);
}
if let Some(underline) = other.underline {
style.underline = Some(underline);
}
if let Some(strikethrough) = other.strikethrough {
style.strikethrough = Some(strikethrough);
}
if let Some(fade_out) = other.fade_out {
style.fade_out = Some(fade_out);
}
}
#[cfg(test)]
mod tests {
use gpui::Hsla;
@ -720,6 +742,7 @@ mod tests {
#[track_caller]
fn assert_unique_styles(
range: Range<usize>,
left: Vec<(Range<usize>, HighlightStyle)>,
right: Vec<(Range<usize>, HighlightStyle)>,
) {
@ -740,7 +763,7 @@ mod tests {
}
}
let left = unique_styles(left);
let left = unique_styles(&range, left);
if left.len() != right.len() {
println!("\n---------------------------------------------");
for (range, style) in left.iter() {
@ -770,18 +793,21 @@ mod tests {
let clean = HighlightStyle::default();
assert_unique_styles(
0..65,
vec![
(0..10, clean),
(0..10, clean),
(2..10, clean),
(2..10, clean),
(5..11, red),
(0..6, clean),
(2..6, clean),
(10..15, green),
(15..30, clean),
(29..35, blue),
(35..40, green),
(45..60, blue),
],
vec![
(0..6, clean),
(0..5, clean),
(5..6, red),
(6..10, red),
(10..11, green),
(11..15, green),
@ -789,6 +815,9 @@ mod tests {
(29..30, blue),
(30..35, blue),
(35..40, green),
(40..45, clean),
(45..60, blue),
(60..65, clean),
],
);
}