In theme, add an InputEditorSyle for styling small editors

Max Brunsfeld and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

gpui/src/fonts.rs              |  2 
zed/assets/themes/_base.toml   | 16 +++++----
zed/assets/themes/dark.toml    |  4 ++
zed/assets/themes/light.toml   |  4 ++
zed/src/chat_panel.rs          |  7 +++
zed/src/editor.rs              |  7 +++
zed/src/editor/display_map.rs  | 22 +++++--------
zed/src/editor/element.rs      | 19 ++++++++---
zed/src/file_finder.rs         | 23 ++++++++-----
zed/src/theme.rs               | 57 ++++++++++++++++++++++-------------
zed/src/theme/highlight_map.rs |  1 
zed/src/theme_selector.rs      | 20 ++++++++----
12 files changed, 115 insertions(+), 67 deletions(-)

Detailed changes

gpui/src/fonts.rs 🔗

@@ -26,7 +26,7 @@ pub struct TextStyle {
     pub font_properties: Properties,
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug)]
 pub struct HighlightStyle {
     pub color: Color,
     pub font_properties: Properties,

zed/assets/themes/_base.toml 🔗

@@ -65,6 +65,11 @@ corner_radius = 6
 border = { color = "#000000", width = 1 }
 background = "$surface.0"
 
+[chat_panel.input_editor]
+text = "$text.1.color"
+background = "$surface.1"
+selection = "$selection.host"
+
 [selector]
 background = "$surface.2"
 text = "$text.0"
@@ -72,6 +77,7 @@ padding = 6
 margin.top = 12
 corner_radius = 6
 shadow = { offset = [0, 0], blur = 12, color = "#00000088" }
+input_editor = "$chat_panel.input_editor"
 
 [selector.item]
 background = "#424344"
@@ -85,15 +91,11 @@ extends = "$selector.item"
 background = "#094771"
 
 [editor]
+text = "$text.1.color"
 background = "$surface.1"
 gutter_background = "$surface.1"
 active_line_background = "$surface.2"
 line_number = "$text.2.color"
 line_number_active = "$text.0.color"
-replicas = [
-    { selection = "#264f78", cursor = "$text.0.color" },
-    { selection = "#504f31", cursor = "#fcf154" },
-]
-
-[syntax]
-default = "$text.1.color"
+selection = "$selection.host"
+guest_selections = "$selection.guests"

zed/assets/themes/dark.toml 🔗

@@ -11,6 +11,10 @@ base = { family = "Helvetica", size = 14.0 }
 1 = { extends = "$text.base", color = "#b3b3b3" }
 2 = { extends = "$text.base", color = "#7b7d80" }
 
+[selection]
+host = { selection = "#264f78", cursor = "$text.0.color" }
+guests = [{ selection = "#504f31", cursor = "#fcf154" }]
+
 [status]
 good = "#4fac63"
 info = "#3c5dd4"

zed/assets/themes/light.toml 🔗

@@ -12,6 +12,10 @@ base = { family = "Helvetica", size = 14.0 }
 1 = { extends = "$text.base", color = "#111111" }
 2 = { extends = "$text.base", color = "#333333" }
 
+[selection]
+host = { selection = "#264f78", cursor = "$text.0.color" }
+guests = [{ selection = "#504f31", cursor = "#fcf154" }]
+
 [status]
 good = "#4fac63"
 info = "#3c5dd4"

zed/src/chat_panel.rs 🔗

@@ -46,7 +46,12 @@ impl ChatPanel {
         settings: watch::Receiver<Settings>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx));
+        let input_editor = cx.add_view(|cx| {
+            Editor::auto_height(settings.clone(), cx).with_style({
+                let settings = settings.clone();
+                move |_| settings.borrow().theme.chat_panel.input_editor.as_editor()
+            })
+        });
         let channel_select = cx.add_view(|cx| {
             let channel_list = channel_list.clone();
             Select::new(0, cx, {

zed/src/editor.rs 🔗

@@ -2397,6 +2397,7 @@ impl Snapshot {
     pub fn layout_lines(
         &mut self,
         mut rows: Range<u32>,
+        style: &EditorStyle,
         font_cache: &FontCache,
         layout_cache: &TextLayoutCache,
     ) -> Result<Vec<text_layout::Line>> {
@@ -2433,7 +2434,11 @@ impl Snapshot {
                 }
 
                 if !line_chunk.is_empty() && !line_exceeded_max_len {
-                    let style = self.theme.syntax.highlight_style(style_ix);
+                    let style = self
+                        .theme
+                        .syntax
+                        .highlight_style(style_ix)
+                        .unwrap_or(style.text.clone());
                     // Avoid a lookup if the font properties match the previous ones.
                     let font_id = if style.font_properties == prev_font_properties {
                         prev_font_id

zed/src/editor/display_map.rs 🔗

@@ -661,13 +661,10 @@ mod tests {
             (function_item name: (identifier) @fn.name)"#,
         )
         .unwrap();
-        let theme = SyntaxTheme::new(
-            Default::default(),
-            vec![
-                ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
-                ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
-            ],
-        );
+        let theme = SyntaxTheme::new(vec![
+            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
+            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+        ]);
         let lang = Arc::new(Language {
             config: LanguageConfig {
                 name: "Test".to_string(),
@@ -754,13 +751,10 @@ mod tests {
             (function_item name: (identifier) @fn.name)"#,
         )
         .unwrap();
-        let theme = SyntaxTheme::new(
-            Default::default(),
-            vec![
-                ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
-                ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
-            ],
-        );
+        let theme = SyntaxTheme::new(vec![
+            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
+            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+        ]);
         let lang = Arc::new(Language {
             config: LanguageConfig {
                 name: "Test".to_string(),

zed/src/editor/element.rs 🔗

@@ -280,7 +280,12 @@ impl EditorElement {
         let content_origin = bounds.origin() + layout.text_offset;
 
         for (replica_id, selections) in &layout.selections {
-            let replica_theme = theme.replicas[*replica_id as usize % theme.replicas.len()];
+            let style_ix = *replica_id as usize % (theme.guest_selections.len() + 1);
+            let style = if style_ix == 0 {
+                &theme.selection
+            } else {
+                &theme.guest_selections[style_ix - 1]
+            };
 
             for selection in selections {
                 if selection.start != selection.end {
@@ -294,7 +299,7 @@ impl EditorElement {
                     };
 
                     let selection = Selection {
-                        color: replica_theme.selection,
+                        color: style.selection,
                         line_height: layout.line_height,
                         start_y: content_origin.y() + row_range.start as f32 * layout.line_height
                             - scroll_top,
@@ -337,7 +342,7 @@ impl EditorElement {
                             - scroll_left;
                         let y = selection.end.row() as f32 * layout.line_height - scroll_top;
                         cursors.push(Cursor {
-                            color: replica_theme.cursor,
+                            color: style.cursor,
                             origin: content_origin + vec2f(x, y),
                             line_height: layout.line_height,
                         });
@@ -507,8 +512,12 @@ impl Element for EditorElement {
         };
 
         let mut max_visible_line_width = 0.0;
-        let line_layouts = match snapshot.layout_lines(start_row..end_row, font_cache, layout_cache)
-        {
+        let line_layouts = match snapshot.layout_lines(
+            start_row..end_row,
+            &self.style,
+            font_cache,
+            layout_cache,
+        ) {
             Err(error) => {
                 log::error!("error laying out lines: {}", error);
                 return (size, None);

zed/src/file_finder.rs 🔗

@@ -30,7 +30,7 @@ pub struct FileFinder {
     handle: WeakViewHandle<Self>,
     settings: watch::Receiver<Settings>,
     workspace: WeakViewHandle<Workspace>,
-    query_buffer: ViewHandle<Editor>,
+    query_editor: ViewHandle<Editor>,
     search_count: usize,
     latest_search_id: usize,
     latest_search_did_cancel: bool,
@@ -86,7 +86,7 @@ impl View for FileFinder {
             ConstrainedBox::new(
                 Container::new(
                     Flex::new(Axis::Vertical)
-                        .with_child(ChildView::new(self.query_buffer.id()).boxed())
+                        .with_child(ChildView::new(self.query_editor.id()).boxed())
                         .with_child(Expanded::new(1.0, self.render_matches()).boxed())
                         .boxed(),
                 )
@@ -102,7 +102,7 @@ impl View for FileFinder {
     }
 
     fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.query_buffer);
+        cx.focus(&self.query_editor);
     }
 
     fn keymap_context(&self, _: &AppContext) -> keymap::Context {
@@ -266,15 +266,20 @@ impl FileFinder {
     ) -> Self {
         cx.observe(&workspace, Self::workspace_updated).detach();
 
-        let query_buffer = cx.add_view(|cx| Editor::single_line(settings.clone(), cx));
-        cx.subscribe(&query_buffer, Self::on_query_editor_event)
+        let query_editor = cx.add_view(|cx| {
+            Editor::single_line(settings.clone(), cx).with_style({
+                let settings = settings.clone();
+                move |_| settings.borrow().theme.selector.input_editor.as_editor()
+            })
+        });
+        cx.subscribe(&query_editor, Self::on_query_editor_event)
             .detach();
 
         Self {
             handle: cx.handle().downgrade(),
             settings,
             workspace: workspace.downgrade(),
-            query_buffer,
+            query_editor,
             search_count: 0,
             latest_search_id: 0,
             latest_search_did_cancel: false,
@@ -287,7 +292,7 @@ impl FileFinder {
     }
 
     fn workspace_updated(&mut self, _: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) {
-        let query = self.query_buffer.update(cx, |buffer, cx| buffer.text(cx));
+        let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
         if let Some(task) = self.spawn_search(query, cx) {
             task.detach();
         }
@@ -301,7 +306,7 @@ impl FileFinder {
     ) {
         match event {
             editor::Event::Edited => {
-                let query = self.query_buffer.update(cx, |buffer, cx| buffer.text(cx));
+                let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
                 if query.is_empty() {
                     self.latest_search_id = util::post_inc(&mut self.search_count);
                     self.matches.clear();
@@ -460,7 +465,7 @@ mod tests {
                 .downcast::<FileFinder>()
                 .unwrap()
         });
-        let query_buffer = cx.read(|cx| finder.read(cx).query_buffer.clone());
+        let query_buffer = cx.read(|cx| finder.read(cx).query_editor.clone());
 
         let chain = vec![finder.id(), query_buffer.id()];
         cx.dispatch_action(window_id, chain.clone(), Insert("b".into()));

zed/src/theme.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     elements::{ContainerStyle, LabelStyle},
     fonts::{HighlightStyle, TextStyle},
 };
-use serde::{de, Deserialize};
+use serde::Deserialize;
 use std::collections::HashMap;
 
 pub use highlight_map::*;
@@ -28,7 +28,6 @@ pub struct Theme {
 
 pub struct SyntaxTheme {
     highlights: Vec<(String, HighlightStyle)>,
-    default_style: HighlightStyle,
 }
 
 #[derive(Deserialize)]
@@ -69,6 +68,7 @@ pub struct ChatPanel {
     pub container: ContainerStyle,
     pub message: ChatMessage,
     pub channel_select: ChannelSelect,
+    pub input_editor: InputEditorStyle,
 }
 
 #[derive(Deserialize)]
@@ -107,6 +107,7 @@ pub struct Selector {
     #[serde(flatten)]
     pub label: LabelStyle,
 
+    pub input_editor: InputEditorStyle,
     pub item: ContainedLabel,
     pub active_item: ContainedLabel,
 }
@@ -129,33 +130,38 @@ pub struct ContainedLabel {
 
 #[derive(Clone, Deserialize)]
 pub struct EditorStyle {
+    pub text: HighlightStyle,
     pub background: Color,
+    pub selection: SelectionStyle,
     pub gutter_background: Color,
     pub active_line_background: Color,
     pub line_number: Color,
     pub line_number_active: Color,
-    pub replicas: Vec<Replica>,
+    pub guest_selections: Vec<SelectionStyle>,
+}
+
+#[derive(Clone, Deserialize)]
+pub struct InputEditorStyle {
+    pub text: HighlightStyle,
+    pub background: Color,
+    pub selection: SelectionStyle,
 }
 
 #[derive(Clone, Copy, Default, Deserialize)]
-pub struct Replica {
+pub struct SelectionStyle {
     pub cursor: Color,
     pub selection: Color,
 }
 
 impl SyntaxTheme {
-    pub fn new(default_style: HighlightStyle, highlights: Vec<(String, HighlightStyle)>) -> Self {
-        Self {
-            default_style,
-            highlights,
-        }
+    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
+        Self { highlights }
     }
 
-    pub fn highlight_style(&self, id: HighlightId) -> HighlightStyle {
+    pub fn highlight_style(&self, id: HighlightId) -> Option<HighlightStyle> {
         self.highlights
             .get(id.0 as usize)
             .map(|entry| entry.1.clone())
-            .unwrap_or_else(|| self.default_style.clone())
     }
 
     #[cfg(test)]
@@ -167,12 +173,28 @@ impl SyntaxTheme {
 impl Default for EditorStyle {
     fn default() -> Self {
         Self {
+            text: HighlightStyle {
+                color: Color::from_u32(0xff0000ff),
+                font_properties: Default::default(),
+            },
             background: Default::default(),
             gutter_background: Default::default(),
             active_line_background: Default::default(),
             line_number: Default::default(),
             line_number_active: Default::default(),
-            replicas: vec![Default::default()],
+            selection: Default::default(),
+            guest_selections: Default::default(),
+        }
+    }
+}
+
+impl InputEditorStyle {
+    pub fn as_editor(&self) -> EditorStyle {
+        EditorStyle {
+            text: self.text.clone(),
+            background: self.background,
+            selection: self.selection,
+            ..Default::default()
         }
     }
 }
@@ -182,16 +204,9 @@ impl<'de> Deserialize<'de> for SyntaxTheme {
     where
         D: serde::Deserializer<'de>,
     {
-        let mut syntax_data: HashMap<String, HighlightStyle> =
-            Deserialize::deserialize(deserializer)?;
-
-        let mut result = Self {
-            highlights: Vec::<(String, HighlightStyle)>::new(),
-            default_style: syntax_data
-                .remove("default")
-                .ok_or_else(|| de::Error::custom("must specify a default color in syntax theme"))?,
-        };
+        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
 
+        let mut result = Self::new(Vec::new());
         for (key, style) in syntax_data {
             match result
                 .highlights

zed/src/theme/highlight_map.rs 🔗

@@ -69,7 +69,6 @@ mod tests {
     #[test]
     fn test_highlight_map() {
         let theme = SyntaxTheme::new(
-            Default::default(),
             [
                 ("function", Color::from_u32(0x100000ff)),
                 ("function.method", Color::from_u32(0x200000ff)),

zed/src/theme_selector.rs 🔗

@@ -25,7 +25,7 @@ pub struct ThemeSelector {
     settings: watch::Receiver<Settings>,
     registry: Arc<ThemeRegistry>,
     matches: Vec<StringMatch>,
-    query_buffer: ViewHandle<Editor>,
+    query_editor: ViewHandle<Editor>,
     list_state: UniformListState,
     selected_index: usize,
 }
@@ -60,15 +60,21 @@ impl ThemeSelector {
         registry: Arc<ThemeRegistry>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let query_buffer = cx.add_view(|cx| Editor::single_line(settings.clone(), cx));
-        cx.subscribe(&query_buffer, Self::on_query_editor_event)
+        let query_editor = cx.add_view(|cx| {
+            Editor::single_line(settings.clone(), cx).with_style({
+                let settings = settings.clone();
+                move |_| settings.borrow().theme.selector.input_editor.as_editor()
+            })
+        });
+
+        cx.subscribe(&query_editor, Self::on_query_editor_event)
             .detach();
 
         let mut this = Self {
             settings,
             settings_tx,
             registry,
-            query_buffer,
+            query_editor,
             matches: Vec::new(),
             list_state: Default::default(),
             selected_index: 0,
@@ -151,7 +157,7 @@ impl ThemeSelector {
                 string: name,
             })
             .collect::<Vec<_>>();
-        let query = self.query_buffer.update(cx, |buffer, cx| buffer.text(cx));
+        let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
 
         self.matches = if query.is_empty() {
             candidates
@@ -276,7 +282,7 @@ impl View for ThemeSelector {
             ConstrainedBox::new(
                 Container::new(
                     Flex::new(Axis::Vertical)
-                        .with_child(ChildView::new(self.query_buffer.id()).boxed())
+                        .with_child(ChildView::new(self.query_editor.id()).boxed())
                         .with_child(Expanded::new(1.0, self.render_matches(cx)).boxed())
                         .boxed(),
                 )
@@ -292,7 +298,7 @@ impl View for ThemeSelector {
     }
 
     fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.query_buffer);
+        cx.focus(&self.query_editor);
     }
 
     fn keymap_context(&self, _: &AppContext) -> keymap::Context {