Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:52:53.720Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

highlighted_label.rs

329 lines · 10.0 KB · rust
1use std::ops::Range;
2
3use gpui::{FontWeight, HighlightStyle, StyleRefinement, StyledText};
4use gpui_util::debug_panic;
5
6use crate::{LabelCommon, LabelLike, LabelSize, LineHeightStyle, prelude::*};
7
8#[derive(IntoElement, RegisterComponent)]
9pub struct HighlightedLabel {
10    base: LabelLike,
11    label: SharedString,
12    highlight_indices: Vec<usize>,
13}
14
15impl HighlightedLabel {
16    /// Constructs a label with the given characters highlighted.
17    /// Characters are identified by UTF-8 byte position.
18    #[track_caller]
19    pub fn new(label: impl Into<SharedString>, mut highlight_indices: Vec<usize>) -> Self {
20        let label = label.into();
21
22        if let Some(index) = highlight_indices
23            .iter()
24            .find(|&i| !label.is_char_boundary(*i))
25        {
26            let location = std::panic::Location::caller();
27            debug_panic!(
28                "highlight index {index} is not a valid UTF-8 boundary (called from {location})"
29            );
30            highlight_indices.clear();
31        }
32
33        Self {
34            base: LabelLike::new(),
35            label,
36            highlight_indices,
37        }
38    }
39
40    /// Constructs a label with the given byte ranges highlighted.
41    /// Assumes that the highlight ranges are valid UTF-8 byte positions.
42    pub fn from_ranges(
43        label: impl Into<SharedString>,
44        highlight_ranges: Vec<Range<usize>>,
45    ) -> Self {
46        let label = label.into();
47        let highlight_indices = highlight_ranges
48            .iter()
49            .flat_map(|range| {
50                let mut indices = Vec::new();
51                let mut index = range.start;
52                while index < range.end {
53                    indices.push(index);
54                    index += label[index..].chars().next().map_or(0, |c| c.len_utf8());
55                }
56                indices
57            })
58            .collect();
59
60        Self {
61            base: LabelLike::new(),
62            label,
63            highlight_indices,
64        }
65    }
66
67    pub fn text(&self) -> &str {
68        self.label.as_str()
69    }
70
71    pub fn highlight_indices(&self) -> &[usize] {
72        &self.highlight_indices
73    }
74
75    /// Truncates the label from the start, keeping the end visible.
76    pub fn truncate_start(mut self) -> Self {
77        self.base = self.base.truncate_start();
78        self
79    }
80
81    /// Truncates overflowing text with an ellipsis (`…`) in the middle if needed.
82    pub fn truncate_middle(mut self) -> Self {
83        self.base = self.base.truncate_middle();
84        self
85    }
86}
87
88impl HighlightedLabel {
89    fn style(&mut self) -> &mut StyleRefinement {
90        self.base.base.style()
91    }
92
93    pub fn flex_1(mut self) -> Self {
94        self.style().flex_grow = Some(1.);
95        self.style().flex_shrink = Some(1.);
96        self.style().flex_basis = Some(gpui::relative(0.).into());
97        self
98    }
99
100    pub fn flex_none(mut self) -> Self {
101        self.style().flex_grow = Some(0.);
102        self.style().flex_shrink = Some(0.);
103        self
104    }
105
106    pub fn flex_grow(mut self) -> Self {
107        self.style().flex_grow = Some(1.);
108        self
109    }
110
111    pub fn flex_shrink(mut self) -> Self {
112        self.style().flex_shrink = Some(1.);
113        self
114    }
115
116    pub fn flex_shrink_0(mut self) -> Self {
117        self.style().flex_shrink = Some(0.);
118        self
119    }
120}
121
122impl LabelCommon for HighlightedLabel {
123    fn size(mut self, size: LabelSize) -> Self {
124        self.base = self.base.size(size);
125        self
126    }
127
128    fn weight(mut self, weight: FontWeight) -> Self {
129        self.base = self.base.weight(weight);
130        self
131    }
132
133    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
134        self.base = self.base.line_height_style(line_height_style);
135        self
136    }
137
138    fn color(mut self, color: Color) -> Self {
139        self.base = self.base.color(color);
140        self
141    }
142
143    fn strikethrough(mut self) -> Self {
144        self.base = self.base.strikethrough();
145        self
146    }
147
148    fn italic(mut self) -> Self {
149        self.base = self.base.italic();
150        self
151    }
152
153    fn alpha(mut self, alpha: f32) -> Self {
154        self.base = self.base.alpha(alpha);
155        self
156    }
157
158    fn underline(mut self) -> Self {
159        self.base = self.base.underline();
160        self
161    }
162
163    fn truncate(mut self) -> Self {
164        self.base = self.base.truncate();
165        self
166    }
167
168    fn single_line(mut self) -> Self {
169        self.base = self.base.single_line();
170        self
171    }
172
173    fn buffer_font(mut self, cx: &App) -> Self {
174        self.base = self.base.buffer_font(cx);
175        self
176    }
177
178    fn inline_code(mut self, cx: &App) -> Self {
179        self.base = self.base.inline_code(cx);
180        self
181    }
182}
183
184pub fn highlight_ranges(
185    text: &str,
186    indices: &[usize],
187    style: HighlightStyle,
188) -> Vec<(Range<usize>, HighlightStyle)> {
189    let mut highlight_indices = indices.iter().copied().peekable();
190    let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
191
192    while let Some(start_ix) = highlight_indices.next() {
193        let mut end_ix = start_ix;
194
195        loop {
196            end_ix += text[end_ix..].chars().next().map_or(0, |c| c.len_utf8());
197            if highlight_indices.next_if(|&ix| ix == end_ix).is_none() {
198                break;
199            }
200        }
201
202        highlights.push((start_ix..end_ix, style));
203    }
204
205    highlights
206}
207
208impl RenderOnce for HighlightedLabel {
209    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
210        let highlight_color = cx.theme().colors().text_accent;
211
212        let highlights = highlight_ranges(
213            &self.label,
214            &self.highlight_indices,
215            HighlightStyle {
216                color: Some(highlight_color),
217                ..Default::default()
218            },
219        );
220
221        let mut text_style = window.text_style();
222        text_style.color = self.base.color.color(cx);
223
224        self.base
225            .child(StyledText::new(self.label).with_default_highlights(&text_style, highlights))
226    }
227}
228
229impl Component for HighlightedLabel {
230    fn scope() -> ComponentScope {
231        ComponentScope::Typography
232    }
233
234    fn name() -> &'static str {
235        "HighlightedLabel"
236    }
237
238    fn description() -> &'static str {
239        "A label with highlighted characters based on specified indices."
240    }
241
242    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
243        v_flex()
244            .gap_6()
245            .children(vec![
246                example_group_with_title(
247                    "Basic Usage",
248                    vec![
249                        single_example(
250                            "Default",
251                            HighlightedLabel::new("Highlighted Text", vec![0, 1, 2, 3])
252                                .into_any_element(),
253                        ),
254                        single_example(
255                            "Custom Color",
256                            HighlightedLabel::new("Colored Highlight", vec![0, 1, 7, 8, 9])
257                                .color(Color::Accent)
258                                .into_any_element(),
259                        ),
260                    ],
261                ),
262                example_group_with_title(
263                    "Styles",
264                    vec![
265                        single_example(
266                            "Bold",
267                            HighlightedLabel::new("Bold Highlight", vec![0, 1, 2, 3])
268                                .weight(FontWeight::BOLD)
269                                .into_any_element(),
270                        ),
271                        single_example(
272                            "Italic",
273                            HighlightedLabel::new("Italic Highlight", vec![0, 1, 6, 7, 8])
274                                .italic()
275                                .into_any_element(),
276                        ),
277                        single_example(
278                            "Underline",
279                            HighlightedLabel::new("Underlined Highlight", vec![0, 1, 10, 11, 12])
280                                .underline()
281                                .into_any_element(),
282                        ),
283                    ],
284                ),
285                example_group_with_title(
286                    "Sizes",
287                    vec![
288                        single_example(
289                            "Small",
290                            HighlightedLabel::new("Small Highlight", vec![0, 1, 5, 6, 7])
291                                .size(LabelSize::Small)
292                                .into_any_element(),
293                        ),
294                        single_example(
295                            "Large",
296                            HighlightedLabel::new("Large Highlight", vec![0, 1, 5, 6, 7])
297                                .size(LabelSize::Large)
298                                .into_any_element(),
299                        ),
300                    ],
301                ),
302                example_group_with_title(
303                    "Special Cases",
304                    vec![
305                        single_example(
306                            "Single Line",
307                            HighlightedLabel::new(
308                                "Single Line Highlight\nWith Newline",
309                                vec![0, 1, 7, 8, 9],
310                            )
311                            .single_line()
312                            .into_any_element(),
313                        ),
314                        single_example(
315                            "Truncate",
316                            HighlightedLabel::new(
317                                "This is a very long text that should be truncated with highlights",
318                                vec![0, 1, 2, 3, 4, 5],
319                            )
320                            .truncate()
321                            .into_any_element(),
322                        ),
323                    ],
324                ),
325            ])
326            .into_any_element()
327    }
328}
329
Served at tenant.openagents/omega Member data and write actions are omitted.