Rework color indicators visual representation (#33605)

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

1e11de48ee/crates/editor/src/display_map/inlay_map.rs (L357)

based on 


41583fb066/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
This commit is contained in:
Kirill Bulatov 2025-06-29 12:43:56 +03:00 committed by GitHub
parent e5bcd720e1
commit 047d515abf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 23 deletions

View file

@ -1,3 +1,5 @@
use crate::display_map::inlay_map::InlayChunk;
use super::{ use super::{
Highlights, Highlights,
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, 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)] #[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 { impl From<FoldId> for ElementId {
fn from(val: FoldId) -> Self { fn from(val: FoldId) -> Self {
@ -1311,7 +1313,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> {
pub struct FoldChunks<'a> { pub struct FoldChunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
inlay_chunks: InlayChunks<'a>, inlay_chunks: InlayChunks<'a>,
inlay_chunk: Option<(InlayOffset, language::Chunk<'a>)>, inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
inlay_offset: InlayOffset, inlay_offset: InlayOffset,
output_offset: FoldOffset, output_offset: FoldOffset,
max_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. // 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 buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
let transform_end = self.transform_cursor.end(&()).1; let transform_end = self.transform_cursor.end(&()).1;
let chunk_end = buffer_chunk_end.min(transform_end); 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_tab: chunk.is_tab,
is_inlay: chunk.is_inlay, is_inlay: chunk.is_inlay,
underline: chunk.underline, underline: chunk.underline,
renderer: None, renderer: inlay_chunk.renderer,
}); });
} }

View file

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

View file

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