Represent theme's syntax colors with string keys

Max Brunsfeld and Marshall Bowers created

Co-authored-by: Marshall Bowers <marshall@zed.dev>

Change summary

crates/gpui2/src/style.rs               | 11 +++
crates/theme2/src/theme2.rs             | 17 ++++-
crates/theme2/src/themes/one_dark.rs    | 30 +++++++-
crates/theme2/src/themes/rose_pine.rs   | 66 ++++++++++++++++----
crates/theme2/src/themes/sandcastle.rs  |  8 --
crates/theme_converter/src/main.rs      | 85 ++++++--------------------
crates/ui2/src/components/breadcrumb.rs | 21 +++---
crates/ui2/src/components/buffer.rs     |  2 
crates/ui2/src/components/toolbar.rs    |  8 +-
crates/ui2/src/static_data.rs           | 40 ++++++------
10 files changed, 156 insertions(+), 132 deletions(-)

Detailed changes

crates/gpui2/src/style.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
     Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
-    FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result,
+    FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, Rgba,
     SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
 };
 use refineable::{Cascade, Refineable};
@@ -417,3 +417,12 @@ impl From<Hsla> for HighlightStyle {
         }
     }
 }
+
+impl From<Rgba> for HighlightStyle {
+    fn from(color: Rgba) -> Self {
+        Self {
+            color: Some(color.into()),
+            ..Default::default()
+        }
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -90,13 +90,22 @@ pub struct Theme {
 
 #[derive(Clone)]
 pub struct SyntaxTheme {
-    pub comment: Hsla,
-    pub string: Hsla,
-    pub function: Hsla,
-    pub keyword: Hsla,
     pub highlights: Vec<(String, HighlightStyle)>,
 }
 
+impl SyntaxTheme {
+    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 color(&self, name: &str) -> Hsla {
+        self.get(name).color.unwrap_or_default()
+    }
+}
+
 #[derive(Clone, Copy)]
 pub struct PlayerTheme {
     pub cursor: Hsla,

crates/theme2/src/themes/one_dark.rs 🔗

@@ -36,11 +36,31 @@ pub fn one_dark() -> Theme {
         text_accent: rgba(0x74ade8ff).into(),
         icon_muted: rgba(0x838994ff).into(),
         syntax: SyntaxTheme {
-            comment: rgba(0x5d636fff).into(),
-            string: rgba(0xa1c181ff).into(),
-            function: rgba(0x73ade9ff).into(),
-            keyword: rgba(0xb477cfff).into(),
-            highlights: vec![],
+            highlights: vec![
+                ("link_text".into(), rgba(0x73ade9ff).into()),
+                ("punctuation.special".into(), rgba(0xb1574bff).into()),
+                ("enum".into(), rgba(0xd07277ff).into()),
+                ("text.literal".into(), rgba(0xa1c181ff).into()),
+                ("string".into(), rgba(0xa1c181ff).into()),
+                ("operator".into(), rgba(0x6eb4bfff).into()),
+                ("constructor".into(), rgba(0x73ade9ff).into()),
+                ("emphasis.strong".into(), rgba(0xbf956aff).into()),
+                ("comment".into(), rgba(0x5d636fff).into()),
+                ("function".into(), rgba(0x73ade9ff).into()),
+                ("number".into(), rgba(0xbf956aff).into()),
+                ("property".into(), rgba(0xd07277ff).into()),
+                ("variable.special".into(), rgba(0xbf956aff).into()),
+                ("primary".into(), rgba(0xacb2beff).into()),
+                ("punctuation.list_marker".into(), rgba(0xd07277ff).into()),
+                ("punctuation".into(), rgba(0xacb2beff).into()),
+                ("type".into(), rgba(0x6eb4bfff).into()),
+                ("variant".into(), rgba(0x73ade9ff).into()),
+                ("constant".into(), rgba(0xdfc184ff).into()),
+                ("title".into(), rgba(0xd07277ff).into()),
+                ("boolean".into(), rgba(0xbf956aff).into()),
+                ("keyword".into(), rgba(0xb477cfff).into()),
+                ("link_uri".into(), rgba(0x6eb4bfff).into()),
+            ],
         },
         status_bar: rgba(0x3b414dff).into(),
         title_bar: rgba(0x3b414dff).into(),

crates/theme2/src/themes/rose_pine.rs 🔗

@@ -36,11 +36,23 @@ pub fn rose_pine() -> Theme {
         text_accent: rgba(0x9bced6ff).into(),
         icon_muted: rgba(0x74708dff).into(),
         syntax: SyntaxTheme {
-            comment: rgba(0x6e6a86ff).into(),
-            string: rgba(0xf5c177ff).into(),
-            function: rgba(0xebbcbaff).into(),
-            keyword: rgba(0x30738fff).into(),
-            highlights: vec![],
+            highlights: vec![
+                ("variable".into(), rgba(0xe0def4ff).into()),
+                ("function.method".into(), rgba(0xebbcbaff).into()),
+                ("title".into(), rgba(0xf5c177ff).into()),
+                ("type.builtin".into(), rgba(0x9ccfd8ff).into()),
+                ("type".into(), rgba(0x9ccfd8ff).into()),
+                ("tag".into(), rgba(0x9ccfd8ff).into()),
+                ("operator".into(), rgba(0x30738fff).into()),
+                ("string".into(), rgba(0xf5c177ff).into()),
+                ("function".into(), rgba(0xebbcbaff).into()),
+                ("comment".into(), rgba(0x6e6a86ff).into()),
+                ("keyword".into(), rgba(0x30738fff).into()),
+                ("boolean".into(), rgba(0xebbcbaff).into()),
+                ("punctuation".into(), rgba(0x908caaff).into()),
+                ("link_text".into(), rgba(0x9ccfd8ff).into()),
+                ("link_uri".into(), rgba(0xebbcbaff).into()),
+            ],
         },
         status_bar: rgba(0x292738ff).into(),
         title_bar: rgba(0x292738ff).into(),
@@ -128,11 +140,23 @@ pub fn rose_pine_dawn() -> Theme {
         text_accent: rgba(0x57949fff).into(),
         icon_muted: rgba(0x706c8cff).into(),
         syntax: SyntaxTheme {
-            comment: rgba(0x9893a5ff).into(),
-            string: rgba(0xea9d34ff).into(),
-            function: rgba(0xd7827dff).into(),
-            keyword: rgba(0x276983ff).into(),
-            highlights: Vec::new(),
+            highlights: vec![
+                ("link_text".into(), rgba(0x55949fff).into()),
+                ("punctuation".into(), rgba(0x797593ff).into()),
+                ("string".into(), rgba(0xea9d34ff).into()),
+                ("variable".into(), rgba(0x575279ff).into()),
+                ("type".into(), rgba(0x55949fff).into()),
+                ("comment".into(), rgba(0x9893a5ff).into()),
+                ("boolean".into(), rgba(0xd7827dff).into()),
+                ("function.method".into(), rgba(0xd7827dff).into()),
+                ("operator".into(), rgba(0x276983ff).into()),
+                ("function".into(), rgba(0xd7827dff).into()),
+                ("keyword".into(), rgba(0x276983ff).into()),
+                ("type.builtin".into(), rgba(0x55949fff).into()),
+                ("tag".into(), rgba(0x55949fff).into()),
+                ("title".into(), rgba(0xea9d34ff).into()),
+                ("link_uri".into(), rgba(0xd7827dff).into()),
+            ],
         },
         status_bar: rgba(0xdcd8d8ff).into(),
         title_bar: rgba(0xdcd8d8ff).into(),
@@ -220,11 +244,23 @@ pub fn rose_pine_moon() -> Theme {
         text_accent: rgba(0x9bced6ff).into(),
         icon_muted: rgba(0x85819eff).into(),
         syntax: SyntaxTheme {
-            comment: rgba(0x6e6a86ff).into(),
-            string: rgba(0xf5c177ff).into(),
-            function: rgba(0xea9a97ff).into(),
-            keyword: rgba(0x3d8fb0ff).into(),
-            highlights: Vec::new(),
+            highlights: vec![
+                ("keyword".into(), rgba(0x3d8fb0ff).into()),
+                ("boolean".into(), rgba(0xea9a97ff).into()),
+                ("function".into(), rgba(0xea9a97ff).into()),
+                ("comment".into(), rgba(0x6e6a86ff).into()),
+                ("title".into(), rgba(0xf5c177ff).into()),
+                ("link_text".into(), rgba(0x9ccfd8ff).into()),
+                ("type".into(), rgba(0x9ccfd8ff).into()),
+                ("type.builtin".into(), rgba(0x9ccfd8ff).into()),
+                ("punctuation".into(), rgba(0x908caaff).into()),
+                ("function.method".into(), rgba(0xea9a97ff).into()),
+                ("tag".into(), rgba(0x9ccfd8ff).into()),
+                ("variable".into(), rgba(0xe0def4ff).into()),
+                ("operator".into(), rgba(0x3d8fb0ff).into()),
+                ("string".into(), rgba(0xf5c177ff).into()),
+                ("link_uri".into(), rgba(0xea9a97ff).into()),
+            ],
         },
         status_bar: rgba(0x38354eff).into(),
         title_bar: rgba(0x38354eff).into(),

crates/theme2/src/themes/sandcastle.rs 🔗

@@ -35,13 +35,7 @@ pub fn sandcastle() -> Theme {
         text_disabled: rgba(0x827568ff).into(),
         text_accent: rgba(0x518b8bff).into(),
         icon_muted: rgba(0xa69782ff).into(),
-        syntax: SyntaxTheme {
-            comment: rgba(0xff00ffff).into(),
-            string: rgba(0xff00ffff).into(),
-            function: rgba(0xff00ffff).into(),
-            keyword: rgba(0xff00ffff).into(),
-            highlights: vec![],
-        },
+        syntax: SyntaxTheme { highlights: vec![] },
         status_bar: rgba(0x333944ff).into(),
         title_bar: rgba(0x333944ff).into(),
         toolbar: rgba(0x282c33ff).into(),

crates/theme_converter/src/main.rs 🔗

@@ -89,64 +89,6 @@ impl From<PlayerThemeColors> for PlayerTheme {
     }
 }
 
-#[derive(Clone, Copy)]
-pub struct SyntaxColor {
-    pub comment: Hsla,
-    pub string: Hsla,
-    pub function: Hsla,
-    pub keyword: Hsla,
-}
-
-impl Debug for SyntaxColor {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("SyntaxColor")
-            .field("comment", &HslaPrinter(self.comment))
-            .field("string", &HslaPrinter(self.string))
-            .field("function", &HslaPrinter(self.function))
-            .field("keyword", &HslaPrinter(self.keyword))
-            .finish()
-    }
-}
-
-impl SyntaxColor {
-    pub fn new(theme: &LegacyTheme) -> Self {
-        Self {
-            comment: theme
-                .syntax
-                .get("comment")
-                .cloned()
-                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
-            string: theme
-                .syntax
-                .get("string")
-                .cloned()
-                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
-            function: theme
-                .syntax
-                .get("function")
-                .cloned()
-                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
-            keyword: theme
-                .syntax
-                .get("keyword")
-                .cloned()
-                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
-        }
-    }
-}
-
-impl From<SyntaxColor> for SyntaxTheme {
-    fn from(value: SyntaxColor) -> Self {
-        Self {
-            comment: value.comment,
-            string: value.string,
-            keyword: value.keyword,
-            function: value.function,
-            highlights: Vec::new(),
-        }
-    }
-}
-
 fn convert_theme(theme: LegacyTheme) -> Result<theme2::Theme> {
     let transparent = hsla(0.0, 0.0, 0.0, 0.0);
 
@@ -194,8 +136,13 @@ fn convert_theme(theme: LegacyTheme) -> Result<theme2::Theme> {
         text_disabled: theme.lowest.base.disabled.foreground,
         text_accent: theme.lowest.accent.default.foreground,
         icon_muted: theme.lowest.variant.default.foreground,
-        syntax: SyntaxColor::new(&theme).into(),
-
+        syntax: SyntaxTheme {
+            highlights: theme
+                .syntax
+                .iter()
+                .map(|(token, color)| (token.clone(), color.clone().into()))
+                .collect(),
+        },
         status_bar: theme.lowest.base.default.background,
         title_bar: theme.lowest.base.default.background,
         toolbar: theme.highest.base.default.background,
@@ -491,11 +438,19 @@ pub struct SyntaxThemePrinter(SyntaxTheme);
 impl Debug for SyntaxThemePrinter {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("SyntaxTheme")
-            .field("comment", &HslaPrinter(self.0.comment))
-            .field("string", &HslaPrinter(self.0.string))
-            .field("function", &HslaPrinter(self.0.function))
-            .field("keyword", &HslaPrinter(self.0.keyword))
-            .field("highlights", &VecPrinter(&self.0.highlights))
+            .field(
+                "highlights",
+                &VecPrinter(
+                    &self
+                        .0
+                        .highlights
+                        .iter()
+                        .map(|(token, highlight)| {
+                            (IntoPrinter(token), HslaPrinter(highlight.color.unwrap()))
+                        })
+                        .collect(),
+                ),
+            )
             .finish()
     }
 }

crates/ui2/src/components/breadcrumb.rs 🔗

@@ -1,8 +1,8 @@
 use std::path::PathBuf;
 
-use gpui2::Div;
 use crate::prelude::*;
 use crate::{h_stack, HighlightedText};
+use gpui2::Div;
 
 #[derive(Clone)]
 pub struct Symbol(pub Vec<HighlightedText>);
@@ -15,10 +15,7 @@ pub struct Breadcrumb {
 
 impl Breadcrumb {
     pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
-        Self {
-            path,
-            symbols,
-        }
+        Self { path, symbols }
     }
 
     fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
@@ -90,7 +87,11 @@ mod stories {
             Self
         }
 
-        fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+        fn render<V: 'static>(
+            self,
+            view_state: &mut V,
+            cx: &mut ViewContext<V>,
+        ) -> impl Component<V> {
             let theme = theme(cx);
 
             Story::container(cx)
@@ -102,21 +103,21 @@ mod stories {
                         Symbol(vec![
                             HighlightedText {
                                 text: "impl ".to_string(),
-                                color: theme.syntax.keyword,
+                                color: theme.syntax.color("keyword"),
                             },
                             HighlightedText {
                                 text: "BreadcrumbStory".to_string(),
-                                color: theme.syntax.function,
+                                color: theme.syntax.color("function"),
                             },
                         ]),
                         Symbol(vec![
                             HighlightedText {
                                 text: "fn ".to_string(),
-                                color: theme.syntax.keyword,
+                                color: theme.syntax.color("keyword"),
                             },
                             HighlightedText {
                                 text: "render".to_string(),
-                                color: theme.syntax.function,
+                                color: theme.syntax.color("function"),
                             },
                         ]),
                     ],

crates/ui2/src/components/buffer.rs 🔗

@@ -166,7 +166,7 @@ impl Buffer {
         let line_number_color = if row.current {
             theme.text
         } else {
-            theme.syntax.comment
+            theme.syntax.get("comment").color.unwrap_or_default()
         };
 
         h_stack()

crates/ui2/src/components/toolbar.rs 🔗

@@ -101,21 +101,21 @@ mod stories {
                                 Symbol(vec![
                                     HighlightedText {
                                         text: "impl ".to_string(),
-                                        color: theme.syntax.keyword,
+                                        color: theme.syntax.color("keyword"),
                                     },
                                     HighlightedText {
                                         text: "ToolbarStory".to_string(),
-                                        color: theme.syntax.function,
+                                        color: theme.syntax.color("function"),
                                     },
                                 ]),
                                 Symbol(vec![
                                     HighlightedText {
                                         text: "fn ".to_string(),
-                                        color: theme.syntax.keyword,
+                                        color: theme.syntax.color("keyword"),
                                     },
                                     HighlightedText {
                                         text: "render".to_string(),
-                                        color: theme.syntax.function,
+                                        color: theme.syntax.color("function"),
                                     },
                                 ]),
                             ],

crates/ui2/src/static_data.rs 🔗

@@ -652,11 +652,11 @@ pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
         vec![Symbol(vec![
             HighlightedText {
                 text: "fn ".to_string(),
-                color: theme.syntax.keyword,
+                color: theme.syntax.color("keyword"),
             },
             HighlightedText {
                 text: "main".to_string(),
-                color: theme.syntax.function,
+                color: theme.syntax.color("function"),
             },
         ])],
         hello_world_rust_buffer_example(&theme),
@@ -686,11 +686,11 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                 highlighted_texts: vec![
                     HighlightedText {
                         text: "fn ".to_string(),
-                        color: theme.syntax.keyword,
+                        color: theme.syntax.color("keyword"),
                     },
                     HighlightedText {
                         text: "main".to_string(),
-                        color: theme.syntax.function,
+                        color: theme.syntax.color("function"),
                     },
                     HighlightedText {
                         text: "() {".to_string(),
@@ -710,7 +710,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Statements here are executed when the compiled binary is called."
                         .to_string(),
-                    color: theme.syntax.comment,
+                    color: theme.syntax.color("comment"),
                 }],
             }),
             cursors: None,
@@ -733,7 +733,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Print text to the console.".to_string(),
-                    color: theme.syntax.comment,
+                    color: theme.syntax.color("comment"),
                 }],
             }),
             cursors: None,
@@ -752,7 +752,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                     },
                     HighlightedText {
                         text: "\"Hello, world!\"".to_string(),
-                        color: theme.syntax.string,
+                        color: theme.syntax.color("string"),
                     },
                     HighlightedText {
                         text: ");".to_string(),
@@ -791,11 +791,11 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut WindowContext) -> Ed
         vec![Symbol(vec![
             HighlightedText {
                 text: "fn ".to_string(),
-                color: theme.syntax.keyword,
+                color: theme.syntax.color("keyword"),
             },
             HighlightedText {
                 text: "main".to_string(),
-                color: theme.syntax.function,
+                color: theme.syntax.color("function"),
             },
         ])],
         hello_world_rust_buffer_with_status_example(&theme),
@@ -825,11 +825,11 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
                 highlighted_texts: vec![
                     HighlightedText {
                         text: "fn ".to_string(),
-                        color: theme.syntax.keyword,
+                        color: theme.syntax.color("keyword"),
                     },
                     HighlightedText {
                         text: "main".to_string(),
-                        color: theme.syntax.function,
+                        color: theme.syntax.color("function"),
                     },
                     HighlightedText {
                         text: "() {".to_string(),
@@ -849,7 +849,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
                 highlighted_texts: vec![HighlightedText {
                     text: "// Statements here are executed when the compiled binary is called."
                         .to_string(),
-                    color: theme.syntax.comment,
+                    color: theme.syntax.color("comment"),
                 }],
             }),
             cursors: None,
@@ -872,7 +872,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Print text to the console.".to_string(),
-                    color: theme.syntax.comment,
+                    color: theme.syntax.color("comment"),
                 }],
             }),
             cursors: None,
@@ -891,7 +891,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
                     },
                     HighlightedText {
                         text: "\"Hello, world!\"".to_string(),
-                        color: theme.syntax.string,
+                        color: theme.syntax.color("string"),
                     },
                     HighlightedText {
                         text: ");".to_string(),
@@ -938,7 +938,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "// Marshall and Nate were here".to_string(),
-                    color: theme.syntax.comment,
+                    color: theme.syntax.color("comment"),
                 }],
             }),
             cursors: None,
@@ -969,7 +969,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                 highlighted_texts: vec![
                     HighlightedText {
                         text: "maxdeviant ".to_string(),
-                        color: theme.syntax.keyword,
+                        color: theme.syntax.color("keyword"),
                     },
                     HighlightedText {
                         text: "in ".to_string(),
@@ -977,7 +977,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                     },
                     HighlightedText {
                         text: "profaned-capital ".to_string(),
-                        color: theme.syntax.function,
+                        color: theme.syntax.color("function"),
                     },
                     HighlightedText {
                         text: "in ".to_string(),
@@ -985,7 +985,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                     },
                     HighlightedText {
                         text: "~/p/zed ".to_string(),
-                        color: theme.syntax.function,
+                        color: theme.syntax.color("function"),
                     },
                     HighlightedText {
                         text: "on ".to_string(),
@@ -993,7 +993,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                     },
                     HighlightedText {
                         text: " gpui2-ui ".to_string(),
-                        color: theme.syntax.keyword,
+                        color: theme.syntax.color("keyword"),
                     },
                 ],
             }),
@@ -1008,7 +1008,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "λ ".to_string(),
-                    color: theme.syntax.string,
+                    color: theme.syntax.color("string"),
                 }],
             }),
             cursors: None,