Rework color indicators visual representation (#33605)

Kirill Bulatov created

Use a div-based rendering code instead of using a text

Closes https://github.com/zed-industries/zed/discussions/33507

Before:
<img width="410" alt="before_dark"
src="https://github.com/user-attachments/assets/66ad63ae-7836-4dc7-8176-a2ff5a38bcd4"
/>
After:
<img width="407" alt="after_dark"
src="https://github.com/user-attachments/assets/0b627da8-461b-4f19-b236-4a69bf5952a0"
/>


Before:
<img width="409" alt="before_light"
src="https://github.com/user-attachments/assets/ebcfabec-fcda-4b63-aee6-c702888f0db4"
/>
After:
<img width="410" alt="after_light"
src="https://github.com/user-attachments/assets/c0da42a1-d6b3-4e08-a56c-9966c07e442d"
/>

The border is not that contrast as in VSCode examples in the issue, but
I'm supposed to use the right thing in

https://github.com/zed-industries/zed/blob/1e11de48eeba01dc12761ec1274f8b0963de5514/crates/editor/src/display_map/inlay_map.rs#L357

based on 


https://github.com/zed-industries/zed/blob/41583fb066629d1e54d600e930be068a68984c5c/crates/theme/src/styles/colors.rs#L16-L17

Another oddity is that the border starts to shrink on `cmd-=`
(`zed::IncreaseBufferFontSize`):

<img width="1244" alt="image"
src="https://github.com/user-attachments/assets/f424edc0-ca0c-4b02-96d4-6da7bf70449a"
/>

but that needs a different part of code to be adjusted hence skipped.

Tailwind CSS example:

<img width="1108" alt="image"
src="https://github.com/user-attachments/assets/10ada4dc-ea8c-46d3-b285-d895bbd6a619"
/>


Release Notes:

- Reworked color indicators visual representation

Change summary

crates/editor/src/display_map/fold_map.rs  | 11 ++-
crates/editor/src/display_map/inlay_map.rs | 73 +++++++++++++++++------
crates/editor/src/editor.rs                |  3 
3 files changed, 64 insertions(+), 23 deletions(-)

Detailed changes

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

@@ -1,3 +1,5 @@
+use crate::display_map::inlay_map::InlayChunk;
+
 use super::{
     Highlights,
     inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
@@ -1060,7 +1062,7 @@ impl sum_tree::Summary for TransformSummary {
 }
 
 #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)]
-pub struct FoldId(usize);
+pub struct FoldId(pub(super) usize);
 
 impl From<FoldId> for ElementId {
     fn from(val: FoldId) -> Self {
@@ -1311,7 +1313,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> {
 pub struct FoldChunks<'a> {
     transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
     inlay_chunks: InlayChunks<'a>,
-    inlay_chunk: Option<(InlayOffset, language::Chunk<'a>)>,
+    inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
     inlay_offset: InlayOffset,
     output_offset: FoldOffset,
     max_output_offset: FoldOffset,
@@ -1403,7 +1405,8 @@ impl<'a> Iterator for FoldChunks<'a> {
         }
 
         // Otherwise, take a chunk from the buffer's text.
-        if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
+        if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
+            let chunk = &mut inlay_chunk.chunk;
             let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
             let transform_end = self.transform_cursor.end(&()).1;
             let chunk_end = buffer_chunk_end.min(transform_end);
@@ -1428,7 +1431,7 @@ impl<'a> Iterator for FoldChunks<'a> {
                 is_tab: chunk.is_tab,
                 is_inlay: chunk.is_inlay,
                 underline: chunk.underline,
-                renderer: None,
+                renderer: inlay_chunk.renderer,
             });
         }
 

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

@@ -1,4 +1,4 @@
-use crate::{HighlightStyles, InlayId};
+use crate::{ChunkRenderer, HighlightStyles, InlayId, display_map::FoldId};
 use collections::BTreeSet;
 use gpui::{Hsla, Rgba};
 use language::{Chunk, Edit, Point, TextSummary};
@@ -8,9 +8,11 @@ use multi_buffer::{
 use std::{
     cmp,
     ops::{Add, AddAssign, Range, Sub, SubAssign},
+    sync::Arc,
 };
 use sum_tree::{Bias, Cursor, SumTree};
 use text::{Patch, Rope};
+use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
 
 use super::{Highlights, custom_highlights::CustomHighlightsChunks};
 
@@ -252,6 +254,13 @@ pub struct InlayChunks<'a> {
     snapshot: &'a InlaySnapshot,
 }
 
+#[derive(Clone)]
+pub struct InlayChunk<'a> {
+    pub chunk: Chunk<'a>,
+    /// Whether the inlay should be customly rendered.
+    pub renderer: Option<ChunkRenderer>,
+}
+
 impl InlayChunks<'_> {
     pub fn seek(&mut self, new_range: Range<InlayOffset>) {
         self.transforms.seek(&new_range.start, Bias::Right, &());
@@ -271,7 +280,7 @@ impl InlayChunks<'_> {
 }
 
 impl<'a> Iterator for InlayChunks<'a> {
-    type Item = Chunk<'a>;
+    type Item = InlayChunk<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.output_offset == self.max_output_offset {
@@ -296,9 +305,12 @@ impl<'a> Iterator for InlayChunks<'a> {
 
                 chunk.text = suffix;
                 self.output_offset.0 += prefix.len();
-                Chunk {
-                    text: prefix,
-                    ..chunk.clone()
+                InlayChunk {
+                    chunk: Chunk {
+                        text: prefix,
+                        ..chunk.clone()
+                    },
+                    renderer: None,
                 }
             }
             Transform::Inlay(inlay) => {
@@ -313,6 +325,7 @@ impl<'a> Iterator for InlayChunks<'a> {
                     }
                 }
 
+                let mut renderer = None;
                 let mut highlight_style = match inlay.id {
                     InlayId::InlineCompletion(_) => {
                         self.highlight_styles.inline_completion.map(|s| {
@@ -325,14 +338,33 @@ impl<'a> Iterator for InlayChunks<'a> {
                     }
                     InlayId::Hint(_) => self.highlight_styles.inlay_hint,
                     InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
-                    InlayId::Color(_) => match inlay.color {
-                        Some(color) => {
-                            let mut style = self.highlight_styles.inlay_hint.unwrap_or_default();
-                            style.color = Some(color);
-                            Some(style)
+                    InlayId::Color(id) => {
+                        if let Some(color) = inlay.color {
+                            renderer = Some(ChunkRenderer {
+                                id: FoldId(id),
+                                render: Arc::new(move |cx| {
+                                    div()
+                                        .w_4()
+                                        .h_4()
+                                        .relative()
+                                        .child(
+                                            div()
+                                                .absolute()
+                                                .right_1()
+                                                .w_3p5()
+                                                .h_3p5()
+                                                .border_2()
+                                                .border_color(cx.theme().colors().border)
+                                                .bg(color),
+                                        )
+                                        .into_any_element()
+                                }),
+                                constrain_width: false,
+                                measured_width: None,
+                            });
                         }
-                        None => self.highlight_styles.inlay_hint,
-                    },
+                        self.highlight_styles.inlay_hint
+                    }
                 };
                 let next_inlay_highlight_endpoint;
                 let offset_in_inlay = self.output_offset - self.transforms.start().0;
@@ -370,11 +402,14 @@ impl<'a> Iterator for InlayChunks<'a> {
 
                 self.output_offset.0 += chunk.len();
 
-                Chunk {
-                    text: chunk,
-                    highlight_style,
-                    is_inlay: true,
-                    ..Default::default()
+                InlayChunk {
+                    chunk: Chunk {
+                        text: chunk,
+                        highlight_style,
+                        is_inlay: true,
+                        ..Chunk::default()
+                    },
+                    renderer,
                 }
             }
         };
@@ -1066,7 +1101,7 @@ impl InlaySnapshot {
     #[cfg(test)]
     pub fn text(&self) -> String {
         self.chunks(Default::default()..self.len(), false, Highlights::default())
-            .map(|chunk| chunk.text)
+            .map(|chunk| chunk.chunk.text)
             .collect()
     }
 
@@ -1704,7 +1739,7 @@ mod tests {
                             ..Highlights::default()
                         },
                     )
-                    .map(|chunk| chunk.text)
+                    .map(|chunk| chunk.chunk.text)
                     .collect::<String>();
                 assert_eq!(
                     actual_text,

crates/editor/src/editor.rs 🔗

@@ -547,6 +547,7 @@ pub enum SoftWrap {
 #[derive(Clone)]
 pub struct EditorStyle {
     pub background: Hsla,
+    pub border: Hsla,
     pub local_player: PlayerColor,
     pub text: TextStyle,
     pub scrollbar_width: Pixels,
@@ -562,6 +563,7 @@ impl Default for EditorStyle {
     fn default() -> Self {
         Self {
             background: Hsla::default(),
+            border: Hsla::default(),
             local_player: PlayerColor::default(),
             text: TextStyle::default(),
             scrollbar_width: Pixels::default(),
@@ -22405,6 +22407,7 @@ impl Render for Editor {
             &cx.entity(),
             EditorStyle {
                 background,
+                border: cx.theme().colors().border,
                 local_player: cx.theme().players().local(),
                 text: text_style,
                 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,