From a9e6b49a15bb9bc2b7f152e5d089b287fe5e650a Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 8 Sep 2025 16:14:07 +0800 Subject: [PATCH] highlighter: Fix #1204 changes cause to incorrect highlight result. (#1218) This PR for fix some incorrect changes in #1204 ## Before image ## After image image --- crates/story/examples/code-editor.rs | 36 ++++++-- crates/ui/src/highlighter/highlighter.rs | 113 ++++++++++++++--------- 2 files changed, 99 insertions(+), 50 deletions(-) diff --git a/crates/story/examples/code-editor.rs b/crates/story/examples/code-editor.rs index 681bca7e..5a82ad7c 100644 --- a/crates/story/examples/code-editor.rs +++ b/crates/story/examples/code-editor.rs @@ -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 { - let default_language = LANGUAGES[0].clone(); + pub fn new(default: Option, window: &mut Window, cx: &mut Context) -> 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 { - cx.new(|cx| Self::new(window, cx)) - } - fn set_markers(&mut self, window: &mut Window, cx: &mut Context) { 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 -- ` + 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, + ); }); } diff --git a/crates/ui/src/highlighter/highlighter.rs b/crates/ui/src/highlighter/highlighter.rs index d1aacd43..b8eb1104 100644 --- a/crates/ui/src/highlighter/highlighter.rs +++ b/crates/ui/src/highlighter/highlighter.rs @@ -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, styles: Vec<(Range, HighlightStyle)>, ) -> Vec<(Range, 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 = boundaries.into_iter().collect(); - let mut result = Vec::with_capacity(boundaries.len().saturating_sub(1)); + let intervals: Vec = 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 = 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, left: Vec<(Range, HighlightStyle)>, right: Vec<(Range, 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), ], ); }