Start work on allowing blocks to be styled

Max Brunsfeld created

Change summary

crates/editor/src/display_map.rs           |  13 ++
crates/editor/src/display_map/block_map.rs |  65 ++++++++++---
crates/editor/src/display_map/wrap_map.rs  |  15 ++
crates/editor/src/element.rs               | 115 +++++++++++++++++------
crates/editor/src/lib.rs                   |  82 +++++++++++-----
crates/theme/src/lib.rs                    |  29 ++++-
crates/zed/assets/themes/_base.toml        |   9 +
7 files changed, 232 insertions(+), 96 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -13,7 +13,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
 use std::{collections::HashSet, ops::Range};
 use sum_tree::Bias;
 use tab_map::TabMap;
-use theme::SyntaxTheme;
+use theme::{BlockStyle, SyntaxTheme};
 use wrap_map::WrapMap;
 
 pub trait ToDisplayPoint {
@@ -172,8 +172,8 @@ impl DisplayMapSnapshot {
         self.buffer_snapshot.len() == 0
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
-        self.blocks_snapshot.buffer_rows(start_row)
+    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
+        self.blocks_snapshot.buffer_rows(start_row, cx)
     }
 
     pub fn buffer_row_count(&self) -> u32 {
@@ -416,6 +416,13 @@ impl ToDisplayPoint for Anchor {
     }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DisplayRow {
+    Buffer(u32),
+    Block(BlockId, Option<BlockStyle>),
+    Wrap,
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

crates/editor/src/display_map/block_map.rs 🔗

@@ -1,4 +1,7 @@
-use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint};
+use super::{
+    wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint},
+    BlockStyle, DisplayRow,
+};
 use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
 use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
 use language::{Buffer, Chunk};
@@ -45,11 +48,12 @@ struct BlockRow(u32);
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 struct WrapRow(u32);
 
-struct Block {
+pub struct Block {
     id: BlockId,
     position: Anchor,
     text: Rope,
     build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
+    build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
     disposition: BlockDisposition,
 }
 
@@ -62,6 +66,7 @@ where
     pub position: P,
     pub text: T,
     pub build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
+    pub build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
     pub disposition: BlockDisposition,
 }
 
@@ -115,6 +120,7 @@ pub struct BufferRows<'a> {
     transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
     input_buffer_rows: wrap_map::BufferRows<'a>,
     output_row: u32,
+    cx: Option<&'a AppContext>,
     started: bool,
 }
 
@@ -415,6 +421,7 @@ impl<'a> BlockMapWriter<'a> {
                     position,
                     text: block.text.into(),
                     build_runs: block.build_runs,
+                    build_style: block.build_style,
                     disposition: block.disposition,
                 }),
             );
@@ -519,7 +526,7 @@ impl BlockSnapshot {
         }
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
+    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
         cursor.seek(&BlockRow(start_row), Bias::Right, &());
         let (output_start, input_start) = cursor.start();
@@ -530,6 +537,7 @@ impl BlockSnapshot {
         };
         let input_start_row = input_start.0 + overshoot;
         BufferRows {
+            cx,
             transforms: cursor,
             input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
             output_row: start_row,
@@ -871,7 +879,7 @@ impl<'a> Iterator for BlockChunks<'a> {
 }
 
 impl<'a> Iterator for BufferRows<'a> {
-    type Item = Option<u32>;
+    type Item = DisplayRow;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.started {
@@ -885,10 +893,13 @@ impl<'a> Iterator for BufferRows<'a> {
         }
 
         let transform = self.transforms.item()?;
-        if transform.is_isomorphic() {
-            Some(self.input_buffer_rows.next().unwrap())
+        if let Some(block) = &transform.block {
+            let style = self
+                .cx
+                .and_then(|cx| block.build_style.as_ref().map(|f| f(cx)));
+            Some(DisplayRow::Block(block.id, style))
         } else {
-            Some(None)
+            Some(self.input_buffer_rows.next().unwrap())
         }
     }
 }
@@ -1006,6 +1017,7 @@ mod tests {
                 id: BlockId(0),
                 position: Anchor::min(),
                 text: "one!\ntwo three\nfour".into(),
+                build_style: None,
                 build_runs: Some(Arc::new(move |_| {
                     vec![(3, red.into()), (6, Default::default()), (5, blue.into())]
                 })),
@@ -1080,25 +1092,28 @@ mod tests {
         let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
 
         let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
-        writer.insert(
+        let block_ids = writer.insert(
             vec![
                 BlockProperties {
                     position: Point::new(1, 0),
                     text: "BLOCK 1",
                     disposition: BlockDisposition::Above,
                     build_runs: None,
+                    build_style: None,
                 },
                 BlockProperties {
                     position: Point::new(1, 2),
                     text: "BLOCK 2",
                     disposition: BlockDisposition::Above,
                     build_runs: None,
+                    build_style: None,
                 },
                 BlockProperties {
                     position: Point::new(3, 2),
                     text: "BLOCK 3",
                     disposition: BlockDisposition::Below,
                     build_runs: None,
+                    build_style: None,
                 },
             ],
             cx,
@@ -1181,8 +1196,16 @@ mod tests {
         );
 
         assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
-            &[Some(0), None, None, Some(1), Some(2), Some(3), None]
+            snapshot.buffer_rows(0, None).collect::<Vec<_>>(),
+            &[
+                DisplayRow::Buffer(0),
+                DisplayRow::Block(block_ids[0], None),
+                DisplayRow::Block(block_ids[1], None),
+                DisplayRow::Buffer(1),
+                DisplayRow::Buffer(2),
+                DisplayRow::Buffer(3),
+                DisplayRow::Block(block_ids[2], None)
+            ]
         );
 
         // Insert a line break, separating two block decorations into separate
@@ -1227,12 +1250,14 @@ mod tests {
                     text: "<BLOCK 1",
                     disposition: BlockDisposition::Above,
                     build_runs: None,
+                    build_style: None,
                 },
                 BlockProperties {
                     position: Point::new(1, 1),
                     text: ">BLOCK 2",
                     disposition: BlockDisposition::Below,
                     build_runs: None,
+                    build_style: None,
                 },
             ],
             cx,
@@ -1325,8 +1350,9 @@ mod tests {
                             BlockProperties {
                                 position,
                                 text,
-                                build_runs: None,
                                 disposition,
+                                build_runs: None,
+                                build_style: None,
                             }
                         })
                         .collect::<Vec<_>>();
@@ -1402,6 +1428,7 @@ mod tests {
                             position: BlockPoint::new(row, column),
                             text: block.text,
                             build_runs: block.build_runs.clone(),
+                            build_style: None,
                             disposition: block.disposition,
                         },
                     )
@@ -1424,7 +1451,7 @@ mod tests {
                     .to_point(WrapPoint::new(row, 0), Bias::Left)
                     .row;
 
-                while let Some((_, block)) = sorted_blocks.peek() {
+                while let Some((block_id, block)) = sorted_blocks.peek() {
                     if block.position.row == row && block.disposition == BlockDisposition::Above {
                         let text = block.text.to_string();
                         let padding = " ".repeat(block.position.column as usize);
@@ -1434,7 +1461,7 @@ mod tests {
                                 expected_text.push_str(line);
                             }
                             expected_text.push('\n');
-                            expected_buffer_rows.push(None);
+                            expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
                         }
                         sorted_blocks.next();
                     } else {
@@ -1443,10 +1470,14 @@ mod tests {
                 }
 
                 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
-                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
+                expected_buffer_rows.push(if soft_wrapped {
+                    DisplayRow::Wrap
+                } else {
+                    DisplayRow::Buffer(buffer_row)
+                });
                 expected_text.push_str(input_line);
 
-                while let Some((_, block)) = sorted_blocks.peek() {
+                while let Some((block_id, block)) = sorted_blocks.peek() {
                     if block.position.row == row && block.disposition == BlockDisposition::Below {
                         let text = block.text.to_string();
                         let padding = " ".repeat(block.position.column as usize);
@@ -1456,7 +1487,7 @@ mod tests {
                                 expected_text.push_str(&padding);
                                 expected_text.push_str(line);
                             }
-                            expected_buffer_rows.push(None);
+                            expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
                         }
                         sorted_blocks.next();
                     } else {
@@ -1480,7 +1511,7 @@ mod tests {
                 );
                 assert_eq!(
                     blocks_snapshot
-                        .buffer_rows(start_row as u32)
+                        .buffer_rows(start_row as u32, None)
                         .collect::<Vec<_>>(),
                     &expected_buffer_rows[start_row..]
                 );

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -2,6 +2,7 @@ use super::{
     fold_map,
     patch::Patch,
     tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint},
+    DisplayRow,
 };
 use gpui::{
     fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
@@ -712,7 +713,11 @@ impl Snapshot {
                     prev_tab_row = tab_point.row();
                     soft_wrapped = false;
                 }
-                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
+                expected_buffer_rows.push(if soft_wrapped {
+                    DisplayRow::Wrap
+                } else {
+                    DisplayRow::Buffer(buffer_row)
+                });
             }
 
             for start_display_row in 0..expected_buffer_rows.len() {
@@ -792,7 +797,7 @@ impl<'a> Iterator for Chunks<'a> {
 }
 
 impl<'a> Iterator for BufferRows<'a> {
-    type Item = Option<u32>;
+    type Item = DisplayRow;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.output_row > self.max_output_row {
@@ -812,7 +817,11 @@ impl<'a> Iterator for BufferRows<'a> {
             self.soft_wrapped = true;
         }
 
-        Some(if soft_wrapped { None } else { Some(buffer_row) })
+        Some(if soft_wrapped {
+            DisplayRow::Wrap
+        } else {
+            DisplayRow::Buffer(buffer_row)
+        })
     }
 }
 

crates/editor/src/element.rs 🔗

@@ -1,6 +1,6 @@
 use super::{
-    DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
-    SelectPhase, Snapshot, MAX_LINE_LEN,
+    DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
+    Select, SelectPhase, Snapshot, MAX_LINE_LEN,
 };
 use clock::ReplicaId;
 use gpui::{
@@ -25,6 +25,7 @@ use std::{
     fmt::Write,
     ops::Range,
 };
+use theme::BlockStyle;
 
 pub struct EditorElement {
     view: WeakViewHandle<Editor>,
@@ -359,6 +360,30 @@ impl EditorElement {
         }
 
         if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
+            // Draw blocks
+            for (ixs, block_style) in &layout.block_layouts {
+                let row = start_row + ixs.start;
+                let origin = content_origin
+                    + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top);
+                let height = ixs.len() as f32 * layout.line_height;
+                cx.scene.push_quad(Quad {
+                    bounds: RectF::new(origin, vec2f(visible_text_bounds.width(), height)),
+                    background: block_style.background,
+                    border: block_style
+                        .border
+                        .map_or(Default::default(), |color| Border {
+                            width: 1.,
+                            color,
+                            overlay: true,
+                            top: true,
+                            right: false,
+                            bottom: true,
+                            left: false,
+                        }),
+                    corner_radius: 0.,
+                });
+            }
+
             // Draw glyphs
             for (ix, line) in layout.line_layouts.iter().enumerate() {
                 let row = start_row + ix as u32;
@@ -401,18 +426,24 @@ impl EditorElement {
             .width()
     }
 
-    fn layout_line_numbers(
+    fn layout_rows(
         &self,
         rows: Range<u32>,
         active_rows: &BTreeMap<u32, bool>,
         snapshot: &Snapshot,
         cx: &LayoutContext,
-    ) -> Vec<Option<text_layout::Line>> {
+    ) -> (
+        Vec<Option<text_layout::Line>>,
+        Vec<(Range<u32>, BlockStyle)>,
+    ) {
         let style = &self.settings.style;
-        let mut layouts = Vec::with_capacity(rows.len());
+        let include_line_numbers = snapshot.mode == EditorMode::Full;
+        let mut last_block_id = None;
+        let mut blocks = Vec::<(Range<u32>, BlockStyle)>::new();
+        let mut line_number_layouts = Vec::with_capacity(rows.len());
         let mut line_number = String::new();
-        for (ix, buffer_row) in snapshot
-            .buffer_rows(rows.start)
+        for (ix, row) in snapshot
+            .buffer_rows(rows.start, cx)
             .take((rows.end - rows.start) as usize)
             .enumerate()
         {
@@ -422,27 +453,46 @@ impl EditorElement {
             } else {
                 style.line_number
             };
-            if let Some(buffer_row) = buffer_row {
-                line_number.clear();
-                write!(&mut line_number, "{}", buffer_row + 1).unwrap();
-                layouts.push(Some(cx.text_layout_cache.layout_str(
-                    &line_number,
-                    style.text.font_size,
-                    &[(
-                        line_number.len(),
-                        RunStyle {
-                            font_id: style.text.font_id,
-                            color,
-                            underline: None,
-                        },
-                    )],
-                )));
-            } else {
-                layouts.push(None);
+            match row {
+                DisplayRow::Buffer(buffer_row) => {
+                    if include_line_numbers {
+                        line_number.clear();
+                        write!(&mut line_number, "{}", buffer_row + 1).unwrap();
+                        line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
+                            &line_number,
+                            style.text.font_size,
+                            &[(
+                                line_number.len(),
+                                RunStyle {
+                                    font_id: style.text.font_id,
+                                    color,
+                                    underline: None,
+                                },
+                            )],
+                        )));
+                    }
+                    last_block_id = None;
+                }
+                DisplayRow::Block(block_id, style) => {
+                    let ix = ix as u32;
+                    if last_block_id == Some(block_id) {
+                        if let Some((row_range, _)) = blocks.last_mut() {
+                            row_range.end += 1;
+                        }
+                    } else if let Some(style) = style {
+                        blocks.push((ix..ix + 1, style));
+                    }
+                    line_number_layouts.push(None);
+                    last_block_id = Some(block_id);
+                }
+                DisplayRow::Wrap => {
+                    line_number_layouts.push(None);
+                    last_block_id = None;
+                }
             }
         }
 
-        layouts
+        (line_number_layouts, blocks)
     }
 
     fn layout_lines(
@@ -541,7 +591,7 @@ impl EditorElement {
                     }
 
                     let underline = if let Some(severity) = chunk.diagnostic {
-                        Some(super::diagnostic_color(severity, style))
+                        Some(super::diagnostic_style(severity, style).text)
                     } else {
                         highlight_style.underline
                     };
@@ -669,11 +719,8 @@ impl Element for EditorElement {
             }
         });
 
-        let line_number_layouts = if snapshot.mode == EditorMode::Full {
-            self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
-        } else {
-            Vec::new()
-        };
+        let (line_number_layouts, block_layouts) =
+            self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
 
         let mut max_visible_line_width = 0.0;
         let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
@@ -695,6 +742,7 @@ impl Element for EditorElement {
             active_rows,
             line_layouts,
             line_number_layouts,
+            block_layouts,
             line_height,
             em_width,
             selections,
@@ -817,6 +865,7 @@ pub struct LayoutState {
     active_rows: BTreeMap<u32, bool>,
     line_layouts: Vec<text_layout::Line>,
     line_number_layouts: Vec<Option<text_layout::Line>>,
+    block_layouts: Vec<(Range<u32>, BlockStyle)>,
     line_height: f32,
     em_width: f32,
     selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
@@ -1071,11 +1120,11 @@ mod tests {
         });
         let element = EditorElement::new(editor.downgrade(), settings);
 
-        let layouts = editor.update(cx, |editor, cx| {
+        let (layouts, _) = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             let mut presenter = cx.build_presenter(window_id, 30.);
             let mut layout_cx = presenter.build_layout_context(false, cx);
-            element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
+            element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx)
         });
         assert_eq!(layouts.len(), 6);
     }

crates/editor/src/lib.rs 🔗

@@ -7,12 +7,11 @@ mod test;
 
 use buffer::rope::TextDimension;
 use clock::ReplicaId;
-pub use display_map::DisplayPoint;
 use display_map::*;
+pub use display_map::{DisplayPoint, DisplayRow};
 pub use element::*;
 use gpui::{
     action,
-    color::Color,
     geometry::vector::{vec2f, Vector2F},
     keymap::Binding,
     text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
@@ -33,7 +32,7 @@ use std::{
     time::Duration,
 };
 use sum_tree::Bias;
-use theme::{EditorStyle, SyntaxTheme};
+use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme};
 use util::post_inc;
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -342,6 +341,7 @@ struct BracketPairState {
 #[derive(Debug)]
 struct ActiveDiagnosticGroup {
     primary_range: Range<Anchor>,
+    group_range: Range<Anchor>,
     block_ids: HashSet<BlockId>,
 }
 
@@ -2238,21 +2238,34 @@ impl Editor {
         loop {
             let next_group = buffer
                 .diagnostics_in_range::<_, usize>(search_start..buffer.len())
-                .filter(|(_, diagnostic)| diagnostic.is_primary)
-                .skip_while(|(range, _)| {
-                    Some(range.end) == active_primary_range.as_ref().map(|r| *r.end())
-                })
-                .next()
-                .map(|(range, diagnostic)| (range, diagnostic.group_id));
+                .find_map(|(range, diagnostic)| {
+                    if diagnostic.is_primary
+                        && Some(range.end) != active_primary_range.as_ref().map(|r| *r.end())
+                    {
+                        Some((range, diagnostic.group_id))
+                    } else {
+                        None
+                    }
+                });
 
             if let Some((primary_range, group_id)) = next_group {
                 self.dismiss_diagnostics(cx);
                 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
                     let buffer = self.buffer.read(cx);
+
+                    let mut group_end = Point::zero();
                     let diagnostic_group = buffer
                         .diagnostic_group::<Point>(group_id)
-                        .map(|(range, diagnostic)| (range, diagnostic.clone()))
+                        .map(|(range, diagnostic)| {
+                            if range.end > group_end {
+                                group_end = range.end;
+                            }
+                            (range, diagnostic.clone())
+                        })
                         .collect::<Vec<_>>();
+
+                    let group_range = buffer.anchor_after(diagnostic_group[0].0.start)
+                        ..buffer.anchor_before(group_end);
                     let primary_range = buffer.anchor_after(primary_range.start)
                         ..buffer.anchor_before(primary_range.end);
 
@@ -2265,12 +2278,24 @@ impl Editor {
                                 BlockProperties {
                                     position: range.start,
                                     text: diagnostic.message.as_str(),
-                                    build_runs: Some(Arc::new(move |cx| {
-                                        let settings = build_settings.borrow()(cx);
-                                        vec![(
-                                            message_len,
-                                            diagnostic_color(severity, &settings.style).into(),
-                                        )]
+                                    build_runs: Some(Arc::new({
+                                        let build_settings = build_settings.clone();
+                                        move |cx| {
+                                            let settings = build_settings.borrow()(cx);
+                                            vec![(
+                                                message_len,
+                                                diagnostic_style(severity, &settings.style)
+                                                    .text
+                                                    .into(),
+                                            )]
+                                        }
+                                    })),
+                                    build_style: Some(Arc::new({
+                                        let build_settings = build_settings.clone();
+                                        move |cx| {
+                                            let settings = build_settings.borrow()(cx);
+                                            diagnostic_style(severity, &settings.style).block
+                                        }
                                     })),
                                     disposition: BlockDisposition::Below,
                                 }
@@ -2282,6 +2307,7 @@ impl Editor {
 
                     Some(ActiveDiagnosticGroup {
                         primary_range,
+                        group_range,
                         block_ids,
                     })
                 });
@@ -2815,8 +2841,8 @@ impl Snapshot {
         self.display_snapshot.buffer_row_count()
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
-        self.display_snapshot.buffer_rows(start_row)
+    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> {
+        self.display_snapshot.buffer_rows(start_row, Some(cx))
     }
 
     pub fn chunks<'a>(
@@ -2893,10 +2919,10 @@ impl EditorSettings {
                     selection: Default::default(),
                     guest_selections: Default::default(),
                     syntax: Default::default(),
-                    error_color: Default::default(),
-                    warning_color: Default::default(),
-                    information_color: Default::default(),
-                    hint_color: Default::default(),
+                    diagnostic_error: Default::default(),
+                    diagnostic_warning: Default::default(),
+                    diagnostic_information: Default::default(),
+                    diagnostic_hint: Default::default(),
                 }
             },
         }
@@ -3020,13 +3046,13 @@ impl SelectionExt for Selection<Point> {
     }
 }
 
-pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color {
+pub fn diagnostic_style(severity: DiagnosticSeverity, style: &EditorStyle) -> DiagnosticStyle {
     match severity {
-        DiagnosticSeverity::ERROR => style.error_color,
-        DiagnosticSeverity::WARNING => style.warning_color,
-        DiagnosticSeverity::INFORMATION => style.information_color,
-        DiagnosticSeverity::HINT => style.hint_color,
-        _ => style.text.color,
+        DiagnosticSeverity::ERROR => style.diagnostic_error,
+        DiagnosticSeverity::WARNING => style.diagnostic_warning,
+        DiagnosticSeverity::INFORMATION => style.diagnostic_information,
+        DiagnosticSeverity::HINT => style.diagnostic_hint,
+        _ => Default::default(),
     }
 }
 

crates/theme/src/lib.rs 🔗

@@ -227,12 +227,19 @@ pub struct EditorStyle {
     pub line_number_active: Color,
     pub guest_selections: Vec<SelectionStyle>,
     pub syntax: Arc<SyntaxTheme>,
-    pub error_color: Color,
-    pub warning_color: Color,
+    pub diagnostic_error: DiagnosticStyle,
+    pub diagnostic_warning: DiagnosticStyle,
     #[serde(default)]
-    pub information_color: Color,
+    pub diagnostic_information: DiagnosticStyle,
     #[serde(default)]
-    pub hint_color: Color,
+    pub diagnostic_hint: DiagnosticStyle,
+}
+
+#[derive(Copy, Clone, Deserialize, Default)]
+pub struct DiagnosticStyle {
+    pub text: Color,
+    #[serde(flatten)]
+    pub block: BlockStyle,
 }
 
 #[derive(Clone, Copy, Default, Deserialize)]
@@ -251,6 +258,12 @@ pub struct InputEditorStyle {
     pub selection: SelectionStyle,
 }
 
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
+pub struct BlockStyle {
+    pub background: Option<Color>,
+    pub border: Option<Color>,
+}
+
 impl EditorStyle {
     pub fn placeholder_text(&self) -> &TextStyle {
         self.placeholder_text.as_ref().unwrap_or(&self.text)
@@ -273,10 +286,10 @@ impl InputEditorStyle {
             line_number_active: Default::default(),
             guest_selections: Default::default(),
             syntax: Default::default(),
-            error_color: Default::default(),
-            warning_color: Default::default(),
-            information_color: Default::default(),
-            hint_color: Default::default(),
+            diagnostic_error: Default::default(),
+            diagnostic_warning: Default::default(),
+            diagnostic_information: Default::default(),
+            diagnostic_hint: Default::default(),
         }
     }
 }

crates/zed/assets/themes/_base.toml 🔗

@@ -173,7 +173,7 @@ corner_radius = 6
 
 [project_panel]
 extends = "$panel"
-padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
+padding.top = 6    # ($workspace.tab.height - $project_panel.entry.height) / 2
 
 [project_panel.entry]
 text = "$text.1"
@@ -236,6 +236,7 @@ line_number_active = "$text.0.color"
 selection = "$selection.host"
 guest_selections = "$selection.guests"
 error_color = "$status.bad"
-warning_color = "$status.warn"
-info_color = "$status.info"
-hint_color = "$status.info"
+diagnostic_error = { text = "$status.bad", border = "#ff0000", background = "#ffdddd" }
+diagnostic_warning = { text = "$status.warn", border = "#ffff00", background = "#ffffdd" }
+diagnostic_info = { text = "$status.info" }
+diagnostic_hint = { text = "$status.info" }