highlighted_label.rs

  1use std::ops::Range;
  2
  3use gpui::{FontWeight, HighlightStyle, StyledText};
  4
  5use crate::{LabelCommon, LabelLike, LabelSize, LineHeightStyle, prelude::*};
  6
  7#[derive(IntoElement, RegisterComponent)]
  8pub struct HighlightedLabel {
  9    base: LabelLike,
 10    label: SharedString,
 11    highlight_indices: Vec<usize>,
 12}
 13
 14impl HighlightedLabel {
 15    /// Constructs a label with the given characters highlighted.
 16    /// Characters are identified by UTF-8 byte position.
 17    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
 18        Self {
 19            base: LabelLike::new(),
 20            label: label.into(),
 21            highlight_indices,
 22        }
 23    }
 24
 25    pub fn text(&self) -> &str {
 26        self.label.as_str()
 27    }
 28
 29    pub fn highlight_indices(&self) -> &[usize] {
 30        &self.highlight_indices
 31    }
 32}
 33
 34impl LabelCommon for HighlightedLabel {
 35    fn size(mut self, size: LabelSize) -> Self {
 36        self.base = self.base.size(size);
 37        self
 38    }
 39
 40    fn weight(mut self, weight: FontWeight) -> Self {
 41        self.base = self.base.weight(weight);
 42        self
 43    }
 44
 45    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
 46        self.base = self.base.line_height_style(line_height_style);
 47        self
 48    }
 49
 50    fn color(mut self, color: Color) -> Self {
 51        self.base = self.base.color(color);
 52        self
 53    }
 54
 55    fn strikethrough(mut self) -> Self {
 56        self.base = self.base.strikethrough();
 57        self
 58    }
 59
 60    fn italic(mut self) -> Self {
 61        self.base = self.base.italic();
 62        self
 63    }
 64
 65    fn alpha(mut self, alpha: f32) -> Self {
 66        self.base = self.base.alpha(alpha);
 67        self
 68    }
 69
 70    fn underline(mut self) -> Self {
 71        self.base = self.base.underline();
 72        self
 73    }
 74
 75    fn truncate(mut self) -> Self {
 76        self.base = self.base.truncate();
 77        self
 78    }
 79
 80    fn single_line(mut self) -> Self {
 81        self.base = self.base.single_line();
 82        self
 83    }
 84
 85    fn buffer_font(mut self, cx: &App) -> Self {
 86        self.base = self.base.buffer_font(cx);
 87        self
 88    }
 89
 90    fn inline_code(mut self, cx: &App) -> Self {
 91        self.base = self.base.inline_code(cx);
 92        self
 93    }
 94}
 95
 96pub fn highlight_ranges(
 97    text: &str,
 98    indices: &[usize],
 99    style: HighlightStyle,
100) -> Vec<(Range<usize>, HighlightStyle)> {
101    let mut highlight_indices = indices.iter().copied().peekable();
102    let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
103
104    while let Some(start_ix) = highlight_indices.next() {
105        let mut end_ix = start_ix;
106
107        loop {
108            end_ix = end_ix + text[end_ix..].chars().next().unwrap().len_utf8();
109            if let Some(&next_ix) = highlight_indices.peek()
110                && next_ix == end_ix
111            {
112                end_ix = next_ix;
113                highlight_indices.next();
114                continue;
115            }
116            break;
117        }
118
119        highlights.push((start_ix..end_ix, style));
120    }
121
122    highlights
123}
124
125impl RenderOnce for HighlightedLabel {
126    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
127        let highlight_color = cx.theme().colors().text_accent;
128
129        let highlights = highlight_ranges(
130            &self.label,
131            &self.highlight_indices,
132            HighlightStyle {
133                color: Some(highlight_color),
134                ..Default::default()
135            },
136        );
137
138        let mut text_style = window.text_style();
139        text_style.color = self.base.color.color(cx);
140
141        self.base
142            .child(StyledText::new(self.label).with_default_highlights(&text_style, highlights))
143    }
144}
145
146impl Component for HighlightedLabel {
147    fn scope() -> ComponentScope {
148        ComponentScope::Typography
149    }
150
151    fn name() -> &'static str {
152        "HighlightedLabel"
153    }
154
155    fn description() -> Option<&'static str> {
156        Some("A label with highlighted characters based on specified indices.")
157    }
158
159    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
160        Some(
161            v_flex()
162                .gap_6()
163                .children(vec![
164                    example_group_with_title(
165                        "Basic Usage",
166                        vec![
167                            single_example(
168                                "Default",
169                                HighlightedLabel::new("Highlighted Text", vec![0, 1, 2, 3]).into_any_element(),
170                            ),
171                            single_example(
172                                "Custom Color",
173                                HighlightedLabel::new("Colored Highlight", vec![0, 1, 7, 8, 9])
174                                    .color(Color::Accent)
175                                    .into_any_element(),
176                            ),
177                        ],
178                    ),
179                    example_group_with_title(
180                        "Styles",
181                        vec![
182                            single_example(
183                                "Bold",
184                                HighlightedLabel::new("Bold Highlight", vec![0, 1, 2, 3])
185                                    .weight(FontWeight::BOLD)
186                                    .into_any_element(),
187                            ),
188                            single_example(
189                                "Italic",
190                                HighlightedLabel::new("Italic Highlight", vec![0, 1, 6, 7, 8])
191                                    .italic()
192                                    .into_any_element(),
193                            ),
194                            single_example(
195                                "Underline",
196                                HighlightedLabel::new("Underlined Highlight", vec![0, 1, 10, 11, 12])
197                                    .underline()
198                                    .into_any_element(),
199                            ),
200                        ],
201                    ),
202                    example_group_with_title(
203                        "Sizes",
204                        vec![
205                            single_example(
206                                "Small",
207                                HighlightedLabel::new("Small Highlight", vec![0, 1, 5, 6, 7])
208                                    .size(LabelSize::Small)
209                                    .into_any_element(),
210                            ),
211                            single_example(
212                                "Large",
213                                HighlightedLabel::new("Large Highlight", vec![0, 1, 5, 6, 7])
214                                    .size(LabelSize::Large)
215                                    .into_any_element(),
216                            ),
217                        ],
218                    ),
219                    example_group_with_title(
220                        "Special Cases",
221                        vec![
222                            single_example(
223                                "Single Line",
224                                HighlightedLabel::new("Single Line Highlight\nWith Newline", vec![0, 1, 7, 8, 9])
225                                    .single_line()
226                                    .into_any_element(),
227                            ),
228                            single_example(
229                                "Truncate",
230                                HighlightedLabel::new("This is a very long text that should be truncated with highlights", vec![0, 1, 2, 3, 4, 5])
231                                    .truncate()
232                                    .into_any_element(),
233                            ),
234                        ],
235                    ),
236                ])
237                .into_any_element()
238        )
239    }
240}