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 += text[end_ix..].chars().next().map_or(0, |c| c.len_utf8());
109            if highlight_indices.next_if(|&ix| ix == end_ix).is_none() {
110                break;
111            }
112        }
113
114        highlights.push((start_ix..end_ix, style));
115    }
116
117    highlights
118}
119
120impl RenderOnce for HighlightedLabel {
121    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
122        let highlight_color = cx.theme().colors().text_accent;
123
124        let highlights = highlight_ranges(
125            &self.label,
126            &self.highlight_indices,
127            HighlightStyle {
128                color: Some(highlight_color),
129                ..Default::default()
130            },
131        );
132
133        let mut text_style = window.text_style();
134        text_style.color = self.base.color.color(cx);
135
136        self.base
137            .child(StyledText::new(self.label).with_default_highlights(&text_style, highlights))
138    }
139}
140
141impl Component for HighlightedLabel {
142    fn scope() -> ComponentScope {
143        ComponentScope::Typography
144    }
145
146    fn name() -> &'static str {
147        "HighlightedLabel"
148    }
149
150    fn description() -> Option<&'static str> {
151        Some("A label with highlighted characters based on specified indices.")
152    }
153
154    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
155        Some(
156            v_flex()
157                .gap_6()
158                .children(vec![
159                    example_group_with_title(
160                        "Basic Usage",
161                        vec![
162                            single_example(
163                                "Default",
164                                HighlightedLabel::new("Highlighted Text", vec![0, 1, 2, 3]).into_any_element(),
165                            ),
166                            single_example(
167                                "Custom Color",
168                                HighlightedLabel::new("Colored Highlight", vec![0, 1, 7, 8, 9])
169                                    .color(Color::Accent)
170                                    .into_any_element(),
171                            ),
172                        ],
173                    ),
174                    example_group_with_title(
175                        "Styles",
176                        vec![
177                            single_example(
178                                "Bold",
179                                HighlightedLabel::new("Bold Highlight", vec![0, 1, 2, 3])
180                                    .weight(FontWeight::BOLD)
181                                    .into_any_element(),
182                            ),
183                            single_example(
184                                "Italic",
185                                HighlightedLabel::new("Italic Highlight", vec![0, 1, 6, 7, 8])
186                                    .italic()
187                                    .into_any_element(),
188                            ),
189                            single_example(
190                                "Underline",
191                                HighlightedLabel::new("Underlined Highlight", vec![0, 1, 10, 11, 12])
192                                    .underline()
193                                    .into_any_element(),
194                            ),
195                        ],
196                    ),
197                    example_group_with_title(
198                        "Sizes",
199                        vec![
200                            single_example(
201                                "Small",
202                                HighlightedLabel::new("Small Highlight", vec![0, 1, 5, 6, 7])
203                                    .size(LabelSize::Small)
204                                    .into_any_element(),
205                            ),
206                            single_example(
207                                "Large",
208                                HighlightedLabel::new("Large Highlight", vec![0, 1, 5, 6, 7])
209                                    .size(LabelSize::Large)
210                                    .into_any_element(),
211                            ),
212                        ],
213                    ),
214                    example_group_with_title(
215                        "Special Cases",
216                        vec![
217                            single_example(
218                                "Single Line",
219                                HighlightedLabel::new("Single Line Highlight\nWith Newline", vec![0, 1, 7, 8, 9])
220                                    .single_line()
221                                    .into_any_element(),
222                            ),
223                            single_example(
224                                "Truncate",
225                                HighlightedLabel::new("This is a very long text that should be truncated with highlights", vec![0, 1, 2, 3, 4, 5])
226                                    .truncate()
227                                    .into_any_element(),
228                            ),
229                        ],
230                    ),
231                ])
232                .into_any_element()
233        )
234    }
235}