From e63bd1ab326c387c9c55e64ba49f4cb9d58b271c Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 25 Mar 2026 17:01:17 +0100 Subject: [PATCH] language: Improve highlight map resolution (#52183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR refactors the highlight map capture name resolution to be faster and more predictable. Speficically, - it changes the capture name matching to explicit prefix matching (e.g., `function.call.whatever.jsx` will now be matched by only `function`, `function.call`, `function.call.whatever` and `function.call.whatever.jsx`). This matches the behavior VSCode has - resolving highlights is now much more efficient, as we now look up captures in a BTreeMap as opposed to searching in a Vector for these. This substantially improves the performance for resolving capture names against themes. With the benchmark added here for creating the HighlightMap, we see quite some improvements: ``` Running benches/highlight_map.rs (target/release/deps/highlight_map-f99da68650aac85b) HighlightMap::new/small_captures/small_theme time: [161.90 ns 162.70 ns 163.55 ns] change: [-39.027% -38.352% -37.742%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 3 (3.00%) high mild HighlightMap::new/small_captures/large_theme time: [231.37 ns 233.02 ns 234.70 ns] change: [-91.570% -91.516% -91.464%] (p = 0.00 < 0.05) Performance has improved. HighlightMap::new/large_captures/small_theme time: [991.82 ns 994.94 ns 998.50 ns] change: [-50.670% -50.443% -50.220%] (p = 0.00 < 0.05) Performance has improved. Found 5 outliers among 100 measurements (5.00%) 5 (5.00%) high mild HighlightMap::new/large_captures/large_theme time: [1.6528 µs 1.6650 µs 1.6784 µs] change: [-91.684% -91.637% -91.593%] (p = 0.00 < 0.05) Performance has improved. Found 1 outliers among 100 measurements (1.00%) 1 (1.00%) low mild ``` For large themes and many capture names, the revised approach is much faster. With that in place, we can also add better fallbacks whenever we change tokens, since e.g. a change from `@variable` to `@preproc` would previously cause tokens to not be highlighted at all, whereas now we can add fallbacks for such cases more efficiently. I'll add this later on to this PR. ## Self-Review Checklist - [X] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [X] Tests cover the new/changed behavior - [X] Performance impact has been considered and is acceptable Release Notes: - Improved resolution speed of theme highlight capture names. This might change highlighting in some rare edge cases, but should overall make highlighting more predicatable. Theme captures will now follow a strict prefix matching, so e.g. function.call.decorator.jsx` will now be matched by only `function`, `function.call`, `function.call.decorator` and `function.call.decorator.jsx` with the most specific capture always taking precedence. --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Gaauwe Rombouts --- Cargo.lock | 1 + .../src/session/running/variable_list.rs | 7 +- crates/editor/src/editor.rs | 6 +- crates/editor/src/semantic_tokens.rs | 5 +- crates/language/Cargo.toml | 5 + crates/language/benches/highlight_map.rs | 144 ++++++++++++++++++ crates/language/src/highlight_map.rs | 34 ++--- .../src/highlights_tree_view.rs | 4 +- crates/onboarding/src/theme_preview.rs | 14 +- crates/theme/src/fallback_themes.rs | 126 ++++++++------- crates/theme/src/styles/syntax.rs | 125 +++++++++------ crates/theme/src/theme.rs | 49 +++--- 12 files changed, 346 insertions(+), 174 deletions(-) create mode 100644 crates/language/benches/highlight_map.rs diff --git a/Cargo.lock b/Cargo.lock index ae05451fb1efde4fcb7a4404d6ff2f0526bd1466..a3b1e7b48ad76de494fb13f10391957ccc604816 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9331,6 +9331,7 @@ dependencies = [ "async-trait", "clock", "collections", + "criterion", "ctor", "diffy", "ec4rs", diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index 8329a6baf04061cc33e8130a4e6b3a33b35267b6..fd8fd736b9e5194d34df3928c0c2983bb40be954 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -1076,7 +1076,12 @@ impl VariableList { presentation_hint: Option<&VariablePresentationHint>, cx: &Context, ) -> VariableColor { - let syntax_color_for = |name| cx.theme().syntax().get(name).color; + let syntax_color_for = |name| { + cx.theme() + .syntax() + .style_for_name(name) + .and_then(|style| style.color) + }; let name = if self.disabled { Some(Color::Disabled.color(cx)) } else { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ee659f2870502a96d1e052035d974b10213f5604..b633b1d77359c6f7888e69ffc21615210f075e13 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -601,7 +601,11 @@ pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle { .inlay_hints .show_background; - let mut style = cx.theme().syntax().get("hint"); + let mut style = cx + .theme() + .syntax() + .style_for_name("hint") + .unwrap_or_default(); if style.color.is_none() { style.color = Some(cx.theme().status().hint); diff --git a/crates/editor/src/semantic_tokens.rs b/crates/editor/src/semantic_tokens.rs index 6a82068410f074c3246f2d84eab9a3576f2e8848..1a895465277d02078f1bf23da21f061a94f94be7 100644 --- a/crates/editor/src/semantic_tokens.rs +++ b/crates/editor/src/semantic_tokens.rs @@ -377,7 +377,10 @@ fn convert_token( for rule in matching { empty = false; - let style = rule.style.iter().find_map(|style| theme.get_opt(style)); + let style = rule + .style + .iter() + .find_map(|style| theme.style_for_name(style)); macro_rules! overwrite { ( diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 37c19172f7c48743e1436ba41e30d0c7ebf99d1d..5cc2550d471750e4b1cc4744a5a91af748a0de91 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -101,6 +101,11 @@ toml.workspace = true unindent.workspace = true util = { workspace = true, features = ["test-support"] } zlog.workspace = true +criterion.workspace = true + +[[bench]] +name = "highlight_map" +harness = false [package.metadata.cargo-machete] ignored = ["tracing"] diff --git a/crates/language/benches/highlight_map.rs b/crates/language/benches/highlight_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..97c7204deec8088575fc79f3c4507d9143a70666 --- /dev/null +++ b/crates/language/benches/highlight_map.rs @@ -0,0 +1,144 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use gpui::rgba; +use language::HighlightMap; +use theme::SyntaxTheme; + +fn syntax_theme(highlight_names: &[&str]) -> SyntaxTheme { + SyntaxTheme::new(highlight_names.iter().enumerate().map(|(i, name)| { + let r = ((i * 37) % 256) as u8; + let g = ((i * 53) % 256) as u8; + let b = ((i * 71) % 256) as u8; + let color = rgba(u32::from_be_bytes([r, g, b, 0xff])); + (name.to_string(), color.into()) + })) +} + +static SMALL_THEME_KEYS: &[&str] = &[ + "comment", "function", "keyword", "string", "type", "variable", +]; + +static LARGE_THEME_KEYS: &[&str] = &[ + "attribute", + "boolean", + "comment", + "comment.doc", + "constant", + "constant.builtin", + "constructor", + "embedded", + "emphasis", + "emphasis.strong", + "function", + "function.builtin", + "function.method", + "function.method.builtin", + "function.special.definition", + "keyword", + "keyword.control", + "keyword.control.conditional", + "keyword.control.import", + "keyword.control.repeat", + "keyword.control.return", + "keyword.modifier", + "keyword.operator", + "label", + "link_text", + "link_uri", + "number", + "operator", + "property", + "punctuation", + "punctuation.bracket", + "punctuation.delimiter", + "punctuation.list_marker", + "punctuation.special", + "string", + "string.escape", + "string.regex", + "string.special", + "string.special.symbol", + "tag", + "text.literal", + "title", + "type", + "type.builtin", + "type.super", + "variable", + "variable.builtin", + "variable.member", + "variable.parameter", + "variable.special", +]; + +static SMALL_CAPTURE_NAMES: &[&str] = &[ + "function", + "keyword", + "string.escape", + "type.builtin", + "variable.builtin", +]; + +static LARGE_CAPTURE_NAMES: &[&str] = &[ + "attribute", + "boolean", + "comment", + "comment.doc", + "constant", + "constant.builtin", + "constructor", + "function", + "function.builtin", + "function.method", + "keyword", + "keyword.control", + "keyword.control.conditional", + "keyword.control.import", + "keyword.modifier", + "keyword.operator", + "label", + "number", + "operator", + "property", + "punctuation.bracket", + "punctuation.delimiter", + "punctuation.special", + "string", + "string.escape", + "string.regex", + "string.special", + "tag", + "type", + "type.builtin", + "variable", + "variable.builtin", + "variable.member", + "variable.parameter", +]; + +fn bench_highlight_map_new(c: &mut Criterion) { + let mut group = c.benchmark_group("HighlightMap::new"); + + for (capture_label, capture_names) in [ + ("small_captures", SMALL_CAPTURE_NAMES as &[&str]), + ("large_captures", LARGE_CAPTURE_NAMES as &[&str]), + ] { + for (theme_label, theme_keys) in [ + ("small_theme", SMALL_THEME_KEYS as &[&str]), + ("large_theme", LARGE_THEME_KEYS as &[&str]), + ] { + let theme = syntax_theme(theme_keys); + group.bench_with_input( + BenchmarkId::new(capture_label, theme_label), + &(capture_names, &theme), + |b, (capture_names, theme)| { + b.iter(|| HighlightMap::new(black_box(capture_names), black_box(theme))); + }, + ); + } + } + + group.finish(); +} + +criterion_group!(benches, bench_highlight_map_new); +criterion_main!(benches); diff --git a/crates/language/src/highlight_map.rs b/crates/language/src/highlight_map.rs index ed9eb5d11d7bc4b156dc9bd660fb10a485129c3d..caab0e47f1e10e565aaeed541d99ed0d729b70a8 100644 --- a/crates/language/src/highlight_map.rs +++ b/crates/language/src/highlight_map.rs @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { - pub(crate) fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { + pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { // For each capture name in the highlight query, find the longest // key in the theme's syntax styles that matches all of the // dot-separated components of the capture name. @@ -20,23 +20,8 @@ impl HighlightMap { .iter() .map(|capture_name| { theme - .highlights - .iter() - .enumerate() - .filter_map(|(i, (key, _))| { - let mut len = 0; - let capture_parts = capture_name.split('.'); - for key_part in key.split('.') { - if capture_parts.clone().any(|part| part == key_part) { - len += 1; - } else { - return None; - } - } - Some((i, len)) - }) - .max_by_key(|(_, len)| *len) - .map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32)) + .highlight_id(capture_name) + .map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, HighlightId) }) .collect(), ) @@ -59,11 +44,11 @@ impl HighlightId { } pub fn style(&self, theme: &SyntaxTheme) -> Option { - theme.highlights.get(self.0 as usize).map(|entry| entry.1) + theme.get(self.0 as usize).cloned() } pub fn name<'a>(&self, theme: &'a SyntaxTheme) -> Option<&'a str> { - theme.highlights.get(self.0 as usize).map(|e| e.0.as_str()) + theme.get_capture_name(self.0 as usize) } } @@ -86,8 +71,8 @@ mod tests { #[test] fn test_highlight_map() { - let theme = SyntaxTheme { - highlights: [ + let theme = SyntaxTheme::new( + [ ("function", rgba(0x100000ff)), ("function.method", rgba(0x200000ff)), ("function.async", rgba(0x300000ff)), @@ -96,9 +81,8 @@ mod tests { ("variable", rgba(0x600000ff)), ] .iter() - .map(|(name, color)| (name.to_string(), (*color).into())) - .collect(), - }; + .map(|(name, color)| (name.to_string(), (*color).into())), + ); let capture_names = &[ "function.special", diff --git a/crates/language_tools/src/highlights_tree_view.rs b/crates/language_tools/src/highlights_tree_view.rs index 8a139958897c261816171c364b6d1f62ccb3b8c6..1e19ee47b3c0c42005a266f7e1cd081aa74b2095 100644 --- a/crates/language_tools/src/highlights_tree_view.rs +++ b/crates/language_tools/src/highlights_tree_view.rs @@ -375,7 +375,9 @@ impl HighlightsTreeView { rule.style .iter() .find(|style_name| { - semantic_theme.get_opt(style_name).is_some() + semantic_theme + .style_for_name(style_name) + .is_some() }) .map(|style_name| { SharedString::from(style_name.clone()) diff --git a/crates/onboarding/src/theme_preview.rs b/crates/onboarding/src/theme_preview.rs index 8bd65d8a2707acdc53333071486f41741398a82a..602695cca6a643d4eb4d3476286bba7fcfe74c40 100644 --- a/crates/onboarding/src/theme_preview.rs +++ b/crates/onboarding/src/theme_preview.rs @@ -87,13 +87,13 @@ impl ThemePreviewTile { let colors = theme.colors(); let syntax = theme.syntax(); - let keyword_color = syntax.get("keyword").color; - let function_color = syntax.get("function").color; - let string_color = syntax.get("string").color; - let comment_color = syntax.get("comment").color; - let variable_color = syntax.get("variable").color; - let type_color = syntax.get("type").color; - let punctuation_color = syntax.get("punctuation").color; + let keyword_color = syntax.style_for_name("keyword").and_then(|s| s.color); + let function_color = syntax.style_for_name("function").and_then(|s| s.color); + let string_color = syntax.style_for_name("string").and_then(|s| s.color); + let comment_color = syntax.style_for_name("comment").and_then(|s| s.color); + let variable_color = syntax.style_for_name("variable").and_then(|s| s.color); + let type_color = syntax.style_for_name("type").and_then(|s| s.color); + let punctuation_color = syntax.style_for_name("punctuation").and_then(|s| s.color); let syntax_colors = [ keyword_color, diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index 72b65f85c9ecb2776fc6066c8b926cfa4bd42929..bfff86b5c614e41711ae1d1be3d9b4aca08cc822 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -314,70 +314,68 @@ pub(crate) fn zed_default_dark() -> Theme { warning_border: yellow, }, player, - syntax: Arc::new(SyntaxTheme { - highlights: vec![ - ("attribute".into(), purple.into()), - ("boolean".into(), orange.into()), - ("comment".into(), gray.into()), - ("comment.doc".into(), gray.into()), - ("constant".into(), yellow.into()), - ("constructor".into(), blue.into()), - ("embedded".into(), HighlightStyle::default()), - ( - "emphasis".into(), - HighlightStyle { - font_style: Some(FontStyle::Italic), - ..HighlightStyle::default() - }, - ), - ( - "emphasis.strong".into(), - HighlightStyle { - font_weight: Some(FontWeight::BOLD), - ..HighlightStyle::default() - }, - ), - ("enum".into(), teal.into()), - ("function".into(), blue.into()), - ("function.method".into(), blue.into()), - ("function.definition".into(), blue.into()), - ("hint".into(), blue.into()), - ("keyword".into(), purple.into()), - ("label".into(), HighlightStyle::default()), - ("link_text".into(), blue.into()), - ( - "link_uri".into(), - HighlightStyle { - color: Some(teal), - font_style: Some(FontStyle::Italic), - ..HighlightStyle::default() - }, - ), - ("number".into(), orange.into()), - ("operator".into(), HighlightStyle::default()), - ("predictive".into(), HighlightStyle::default()), - ("preproc".into(), HighlightStyle::default()), - ("primary".into(), HighlightStyle::default()), - ("property".into(), red.into()), - ("punctuation".into(), HighlightStyle::default()), - ("punctuation.bracket".into(), HighlightStyle::default()), - ("punctuation.delimiter".into(), HighlightStyle::default()), - ("punctuation.list_marker".into(), HighlightStyle::default()), - ("punctuation.special".into(), HighlightStyle::default()), - ("string".into(), green.into()), - ("string.escape".into(), HighlightStyle::default()), - ("string.regex".into(), red.into()), - ("string.special".into(), HighlightStyle::default()), - ("string.special.symbol".into(), HighlightStyle::default()), - ("tag".into(), HighlightStyle::default()), - ("text.literal".into(), HighlightStyle::default()), - ("title".into(), HighlightStyle::default()), - ("type".into(), teal.into()), - ("variable".into(), HighlightStyle::default()), - ("variable.special".into(), red.into()), - ("variant".into(), HighlightStyle::default()), - ], - }), + syntax: Arc::new(SyntaxTheme::new(vec![ + ("attribute".into(), purple.into()), + ("boolean".into(), orange.into()), + ("comment".into(), gray.into()), + ("comment.doc".into(), gray.into()), + ("constant".into(), yellow.into()), + ("constructor".into(), blue.into()), + ("embedded".into(), HighlightStyle::default()), + ( + "emphasis".into(), + HighlightStyle { + font_style: Some(FontStyle::Italic), + ..HighlightStyle::default() + }, + ), + ( + "emphasis.strong".into(), + HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..HighlightStyle::default() + }, + ), + ("enum".into(), teal.into()), + ("function".into(), blue.into()), + ("function.method".into(), blue.into()), + ("function.definition".into(), blue.into()), + ("hint".into(), blue.into()), + ("keyword".into(), purple.into()), + ("label".into(), HighlightStyle::default()), + ("link_text".into(), blue.into()), + ( + "link_uri".into(), + HighlightStyle { + color: Some(teal), + font_style: Some(FontStyle::Italic), + ..HighlightStyle::default() + }, + ), + ("number".into(), orange.into()), + ("operator".into(), HighlightStyle::default()), + ("predictive".into(), HighlightStyle::default()), + ("preproc".into(), HighlightStyle::default()), + ("primary".into(), HighlightStyle::default()), + ("property".into(), red.into()), + ("punctuation".into(), HighlightStyle::default()), + ("punctuation.bracket".into(), HighlightStyle::default()), + ("punctuation.delimiter".into(), HighlightStyle::default()), + ("punctuation.list_marker".into(), HighlightStyle::default()), + ("punctuation.special".into(), HighlightStyle::default()), + ("string".into(), green.into()), + ("string.escape".into(), HighlightStyle::default()), + ("string.regex".into(), red.into()), + ("string.special".into(), HighlightStyle::default()), + ("string.special.symbol".into(), HighlightStyle::default()), + ("tag".into(), HighlightStyle::default()), + ("text.literal".into(), HighlightStyle::default()), + ("title".into(), HighlightStyle::default()), + ("type".into(), teal.into()), + ("variable".into(), HighlightStyle::default()), + ("variable.special".into(), red.into()), + ("variant".into(), HighlightStyle::default()), + ])), }, } } diff --git a/crates/theme/src/styles/syntax.rs b/crates/theme/src/styles/syntax.rs index 6a1615387835e0db1aefa03c63efd5c27ca2518d..aa2590547c204ccd33871c00f74dc961470fdd4b 100644 --- a/crates/theme/src/styles/syntax.rs +++ b/crates/theme/src/styles/syntax.rs @@ -1,15 +1,38 @@ #![allow(missing_docs)] -use std::sync::Arc; +use std::{ + collections::{BTreeMap, btree_map::Entry}, + sync::Arc, +}; -use gpui::{HighlightStyle, Hsla}; +use gpui::HighlightStyle; +#[cfg(any(test, feature = "test-support"))] +use gpui::Hsla; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct SyntaxTheme { - pub highlights: Vec<(String, HighlightStyle)>, + pub(self) highlights: Vec, + pub(self) capture_name_map: BTreeMap, } impl SyntaxTheme { + pub fn new(highlights: impl IntoIterator) -> Self { + let (capture_names, highlights) = highlights.into_iter().unzip(); + + Self { + capture_name_map: Self::create_capture_name_map(capture_names), + highlights, + } + } + + fn create_capture_name_map(highlights: Vec) -> BTreeMap { + highlights + .into_iter() + .enumerate() + .map(|(i, key)| (key, i)) + .collect() + } + #[cfg(any(test, feature = "test-support"))] pub fn new_test(colors: impl IntoIterator) -> Self { Self::new_test_styles(colors.into_iter().map(|(key, color)| { @@ -27,34 +50,45 @@ impl SyntaxTheme { pub fn new_test_styles( colors: impl IntoIterator, ) -> Self { - Self { - highlights: colors + Self::new( + colors .into_iter() - .map(|(key, style)| (key.to_owned(), style)) - .collect(), - } + .map(|(key, style)| (key.to_owned(), style)), + ) } - pub fn get(&self, name: &str) -> HighlightStyle { - self.highlights - .iter() - .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) - .unwrap_or_default() + pub fn get(&self, highlight_index: usize) -> Option<&HighlightStyle> { + self.highlights.get(highlight_index) } - pub fn get_opt(&self, name: &str) -> Option { - self.highlights - .iter() - .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) + pub fn style_for_name(&self, name: &str) -> Option { + self.capture_name_map + .get(name) + .map(|highlight_idx| self.highlights[*highlight_idx]) } - pub fn color(&self, name: &str) -> Hsla { - self.get(name).color.unwrap_or_default() + pub fn get_capture_name(&self, idx: usize) -> Option<&str> { + self.capture_name_map + .iter() + .find(|(_, value)| **value == idx) + .map(|(key, _)| key.as_ref()) } - pub fn highlight_id(&self, name: &str) -> Option { - let ix = self.highlights.iter().position(|entry| entry.0 == name)?; - Some(ix as u32) + pub fn highlight_id(&self, capture_name: &str) -> Option { + self.capture_name_map + .range::(( + capture_name.split(".").next().map_or( + std::ops::Bound::Included(capture_name), + std::ops::Bound::Included, + ), + std::ops::Bound::Included(capture_name), + )) + .rfind(|(prefix, _)| { + capture_name + .strip_prefix(*prefix) + .is_some_and(|remainder| remainder.is_empty() || remainder.starts_with('.')) + }) + .map(|(_, index)| *index as u32) } /// Returns a new [`Arc`] with the given syntax styles merged in. @@ -63,33 +97,36 @@ impl SyntaxTheme { return base; } - let mut merged_highlights = base.highlights.clone(); + let mut base = Arc::try_unwrap(base).unwrap_or_else(|base| (*base).clone()); for (name, highlight) in user_syntax_styles { - if let Some((_, existing_highlight)) = merged_highlights - .iter_mut() - .find(|(existing_name, _)| existing_name == &name) - { - existing_highlight.color = highlight.color.or(existing_highlight.color); - existing_highlight.font_weight = - highlight.font_weight.or(existing_highlight.font_weight); - existing_highlight.font_style = - highlight.font_style.or(existing_highlight.font_style); - existing_highlight.background_color = highlight - .background_color - .or(existing_highlight.background_color); - existing_highlight.underline = highlight.underline.or(existing_highlight.underline); - existing_highlight.strikethrough = - highlight.strikethrough.or(existing_highlight.strikethrough); - existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out); - } else { - merged_highlights.push((name, highlight)); + match base.capture_name_map.entry(name) { + Entry::Occupied(entry) => { + if let Some(existing_highlight) = base.highlights.get_mut(*entry.get()) { + existing_highlight.color = highlight.color.or(existing_highlight.color); + existing_highlight.font_weight = + highlight.font_weight.or(existing_highlight.font_weight); + existing_highlight.font_style = + highlight.font_style.or(existing_highlight.font_style); + existing_highlight.background_color = highlight + .background_color + .or(existing_highlight.background_color); + existing_highlight.underline = + highlight.underline.or(existing_highlight.underline); + existing_highlight.strikethrough = + highlight.strikethrough.or(existing_highlight.strikethrough); + existing_highlight.fade_out = + highlight.fade_out.or(existing_highlight.fade_out); + } + } + Entry::Vacant(vacant) => { + vacant.insert(base.highlights.len()); + base.highlights.push(highlight); + } } } - Arc::new(Self { - highlights: merged_highlights, - }) + Arc::new(base) } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ca330beee3c9604278ce187e0609f60fbc58170e..3449e039a9e3f4135a0f8471b8346f6b6e6b9fcc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -258,30 +258,25 @@ impl ThemeFamily { }; refined_accent_colors.merge(&theme.style.accents); - let syntax_highlights = theme - .style - .syntax - .iter() - .map(|(syntax_token, highlight)| { - ( - syntax_token.clone(), - HighlightStyle { - color: highlight - .color - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - background_color: highlight - .background_color - .as_ref() - .and_then(|color| try_parse_color(color).ok()), - font_style: highlight.font_style.map(|s| s.into_gpui()), - font_weight: highlight.font_weight.map(|w| w.into_gpui()), - ..Default::default() - }, - ) - }) - .collect::>(); - let syntax_theme = SyntaxTheme::merge(Arc::new(SyntaxTheme::default()), syntax_highlights); + let syntax_highlights = theme.style.syntax.iter().map(|(syntax_token, highlight)| { + ( + syntax_token.clone(), + HighlightStyle { + color: highlight + .color + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + background_color: highlight + .background_color + .as_ref() + .and_then(|color| try_parse_color(color).ok()), + font_style: highlight.font_style.map(|s| s.into_gpui()), + font_weight: highlight.font_weight.map(|w| w.into_gpui()), + ..Default::default() + }, + ) + }); + let syntax_theme = Arc::new(SyntaxTheme::new(syntax_highlights)); let window_background_appearance = theme .style @@ -381,12 +376,6 @@ impl Theme { &self.styles.status } - /// Returns the color for the syntax node with the given name. - #[inline(always)] - pub fn syntax_color(&self, name: &str) -> Hsla { - self.syntax().color(name) - } - /// Returns the [`Appearance`] for the theme. #[inline(always)] pub fn appearance(&self) -> Appearance {