Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:47:12.135Z 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

label.rs

423 lines · 13.3 KB · rust
1use std::ops::Range;
2
3use crate::{LabelLike, prelude::*};
4use gpui::{HighlightStyle, StyleRefinement, StyledText};
5
6/// A struct representing a label element in the UI.
7///
8/// The `Label` struct stores the label text and common properties for a label element.
9/// It provides methods for modifying these properties.
10///
11/// # Examples
12///
13/// ```
14/// use ui::prelude::*;
15///
16/// Label::new("Hello, World!");
17/// ```
18///
19/// **A colored label**, for example labeling a dangerous action:
20///
21/// ```
22/// use ui::prelude::*;
23///
24/// let my_label = Label::new("Delete").color(Color::Error);
25/// ```
26///
27/// **A label with a strikethrough**, for example labeling something that has been deleted:
28///
29/// ```
30/// use ui::prelude::*;
31///
32/// let my_label = Label::new("Deleted").strikethrough();
33/// ```
34#[derive(IntoElement, RegisterComponent)]
35pub struct Label {
36    base: LabelLike,
37    label: SharedString,
38    render_code_spans: bool,
39}
40
41impl Label {
42    /// Creates a new [`Label`] with the given text.
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// use ui::prelude::*;
48    ///
49    /// let my_label = Label::new("Hello, World!");
50    /// ```
51    pub fn new(label: impl Into<SharedString>) -> Self {
52        Self {
53            base: LabelLike::new(),
54            label: label.into(),
55            render_code_spans: false,
56        }
57    }
58
59    /// When enabled, text wrapped in backticks (e.g. `` `code` ``) will be
60    /// rendered in the buffer (monospace) font.
61    pub fn render_code_spans(mut self) -> Self {
62        self.render_code_spans = true;
63        self
64    }
65
66    /// Sets the text of the [`Label`].
67    pub fn set_text(&mut self, text: impl Into<SharedString>) {
68        self.label = text.into();
69    }
70
71    /// Truncates the label from the start, keeping the end visible.
72    pub fn truncate_start(mut self) -> Self {
73        self.base = self.base.truncate_start();
74        self
75    }
76
77    /// Truncates overflowing text with an ellipsis (`…`) in the middle if needed.
78    pub fn truncate_middle(mut self) -> Self {
79        self.base = self.base.truncate_middle();
80        self
81    }
82
83    /// Wraps the text and truncates it with an ellipsis (`…`) at the end of
84    /// the last visible line if it exceeds the given number of lines.
85    pub fn line_clamp(mut self, lines: usize) -> Self {
86        self.base = self.base.line_clamp(lines);
87        self
88    }
89}
90
91// Style methods.
92impl Label {
93    fn style(&mut self) -> &mut StyleRefinement {
94        self.base.base.style()
95    }
96
97    gpui::margin_style_methods!({
98        visibility: pub
99    });
100
101    pub fn flex_1(mut self) -> Self {
102        self.style().flex_grow = Some(1.);
103        self.style().flex_shrink = Some(1.);
104        self.style().flex_basis = Some(gpui::relative(0.).into());
105        self
106    }
107
108    pub fn flex_none(mut self) -> Self {
109        self.style().flex_grow = Some(0.);
110        self.style().flex_shrink = Some(0.);
111        self
112    }
113
114    pub fn flex_grow(mut self) -> Self {
115        self.style().flex_grow = Some(1.);
116        self
117    }
118
119    pub fn flex_shrink(mut self) -> Self {
120        self.style().flex_shrink = Some(1.);
121        self
122    }
123
124    pub fn flex_shrink_0(mut self) -> Self {
125        self.style().flex_shrink = Some(0.);
126        self
127    }
128}
129
130impl LabelCommon for Label {
131    /// Sets the size of the label using a [`LabelSize`].
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use ui::prelude::*;
137    ///
138    /// let my_label = Label::new("Hello, World!").size(LabelSize::Small);
139    /// ```
140    fn size(mut self, size: LabelSize) -> Self {
141        self.base = self.base.size(size);
142        self
143    }
144
145    /// Sets the weight of the label using a [`FontWeight`].
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use gpui::FontWeight;
151    /// use ui::prelude::*;
152    ///
153    /// let my_label = Label::new("Hello, World!").weight(FontWeight::BOLD);
154    /// ```
155    fn weight(mut self, weight: gpui::FontWeight) -> Self {
156        self.base = self.base.weight(weight);
157        self
158    }
159
160    /// Sets the line height style of the label using a [`LineHeightStyle`].
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// use ui::prelude::*;
166    ///
167    /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel);
168    /// ```
169    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
170        self.base = self.base.line_height_style(line_height_style);
171        self
172    }
173
174    /// Sets the color of the label using a [`Color`].
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use ui::prelude::*;
180    ///
181    /// let my_label = Label::new("Hello, World!").color(Color::Accent);
182    /// ```
183    fn color(mut self, color: Color) -> Self {
184        self.base = self.base.color(color);
185        self
186    }
187
188    /// Sets the strikethrough property of the label.
189    ///
190    /// # Examples
191    ///
192    /// ```
193    /// use ui::prelude::*;
194    ///
195    /// let my_label = Label::new("Hello, World!").strikethrough();
196    /// ```
197    fn strikethrough(mut self) -> Self {
198        self.base = self.base.strikethrough();
199        self
200    }
201
202    /// Sets the italic property of the label.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use ui::prelude::*;
208    ///
209    /// let my_label = Label::new("Hello, World!").italic();
210    /// ```
211    fn italic(mut self) -> Self {
212        self.base = self.base.italic();
213        self
214    }
215
216    /// Sets the alpha property of the color of label.
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use ui::prelude::*;
222    ///
223    /// let my_label = Label::new("Hello, World!").alpha(0.5);
224    /// ```
225    fn alpha(mut self, alpha: f32) -> Self {
226        self.base = self.base.alpha(alpha);
227        self
228    }
229
230    fn underline(mut self) -> Self {
231        self.base = self.base.underline();
232        self
233    }
234
235    /// Truncates overflowing text with an ellipsis (`…`) if needed.
236    fn truncate(mut self) -> Self {
237        self.base = self.base.truncate();
238        self
239    }
240
241    fn single_line(mut self) -> Self {
242        self.label = SharedString::from(self.label.replace('\n', "⏎"));
243        self.base = self.base.single_line();
244        self
245    }
246
247    fn buffer_font(mut self, cx: &App) -> Self {
248        self.base = self.base.buffer_font(cx);
249        self
250    }
251
252    /// Styles the label to look like inline code.
253    fn inline_code(mut self, cx: &App) -> Self {
254        self.base = self.base.inline_code(cx);
255        self
256    }
257}
258
259impl RenderOnce for Label {
260    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
261        if self.render_code_spans {
262            if let Some((stripped, code_ranges)) = parse_backtick_spans(&self.label) {
263                let buffer_font_family = theme::theme_settings(cx).buffer_font(cx).family.clone();
264                let background_color = cx.theme().colors().element_background;
265
266                let highlights = code_ranges.iter().map(|range| {
267                    (
268                        range.clone(),
269                        HighlightStyle {
270                            background_color: Some(background_color),
271                            ..Default::default()
272                        },
273                    )
274                });
275
276                let font_overrides = code_ranges
277                    .iter()
278                    .map(|range| (range.clone(), buffer_font_family.clone()));
279
280                return self.base.child(
281                    StyledText::new(stripped)
282                        .with_highlights(highlights)
283                        .with_font_family_overrides(font_overrides),
284                );
285            }
286        }
287        self.base.child(self.label)
288    }
289}
290
291/// Parses backtick-delimited code spans from a string.
292///
293/// Returns `None` if there are no matched backtick pairs.
294/// Otherwise returns the text with backticks stripped and the byte ranges
295/// of the code spans in the stripped string.
296fn parse_backtick_spans(text: &str) -> Option<(SharedString, Vec<Range<usize>>)> {
297    if !text.contains('`') {
298        return None;
299    }
300
301    let mut stripped = String::with_capacity(text.len());
302    let mut code_ranges = Vec::new();
303    let mut in_code = false;
304    let mut code_start = 0;
305
306    for ch in text.chars() {
307        if ch == '`' {
308            if in_code {
309                code_ranges.push(code_start..stripped.len());
310            } else {
311                code_start = stripped.len();
312            }
313            in_code = !in_code;
314        } else {
315            stripped.push(ch);
316        }
317    }
318
319    if code_ranges.is_empty() {
320        return None;
321    }
322
323    Some((SharedString::from(stripped), code_ranges))
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn test_parse_backtick_spans_no_backticks() {
332        assert_eq!(parse_backtick_spans("plain text"), None);
333    }
334
335    #[test]
336    fn test_parse_backtick_spans_single_span() {
337        let (text, ranges) = parse_backtick_spans("use `zed` to open").unwrap();
338        assert_eq!(text.as_ref(), "use zed to open");
339        assert_eq!(ranges, vec![4..7]);
340    }
341
342    #[test]
343    fn test_parse_backtick_spans_multiple_spans() {
344        let (text, ranges) = parse_backtick_spans("flags `-e` or `-n`").unwrap();
345        assert_eq!(text.as_ref(), "flags -e or -n");
346        assert_eq!(ranges, vec![6..8, 12..14]);
347    }
348
349    #[test]
350    fn test_parse_backtick_spans_unmatched_backtick() {
351        // A trailing unmatched backtick should not produce a code range
352        assert_eq!(parse_backtick_spans("trailing `backtick"), None);
353    }
354
355    #[test]
356    fn test_parse_backtick_spans_empty_span() {
357        let (text, ranges) = parse_backtick_spans("empty `` span").unwrap();
358        assert_eq!(text.as_ref(), "empty  span");
359        assert_eq!(ranges, vec![6..6]);
360    }
361}
362
363impl Component for Label {
364    fn scope() -> ComponentScope {
365        ComponentScope::Typography
366    }
367
368    fn description() -> &'static str {
369        "A text label component that supports various styles, \
370        sizes, and formatting options."
371    }
372
373    fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
374        v_flex()
375                .gap_6()
376                .children(vec![
377                    example_group_with_title(
378                        "Sizes",
379                        vec![
380                            single_example("Default", Label::new("Project Explorer").into_any_element()),
381                            single_example("Small", Label::new("File: main.rs").size(LabelSize::Small).into_any_element()),
382                            single_example("Large", Label::new("Welcome to Omega").size(LabelSize::Large).into_any_element()),
383                        ],
384                    ),
385                    example_group_with_title(
386                        "Colors",
387                        vec![
388                            single_example("Default", Label::new("Status: Ready").into_any_element()),
389                            single_example("Accent", Label::new("New Update Available").color(Color::Accent).into_any_element()),
390                            single_example("Error", Label::new("Build Failed").color(Color::Error).into_any_element()),
391                        ],
392                    ),
393                    example_group_with_title(
394                        "Styles",
395                        vec![
396                            single_example("Default", Label::new("Normal Text").into_any_element()),
397                            single_example("Bold", Label::new("Important Notice").weight(gpui::FontWeight::BOLD).into_any_element()),
398                            single_example("Italic", Label::new("Code Comment").italic().into_any_element()),
399                            single_example("Strikethrough", Label::new("Deprecated Feature").strikethrough().into_any_element()),
400                            single_example("Underline", Label::new("Clickable Link").underline().into_any_element()),
401                            single_example("Inline Code", Label::new("fn main() {}").inline_code(cx).into_any_element()),
402                        ],
403                    ),
404                    example_group_with_title(
405                        "Line Height Styles",
406                        vec![
407                            single_example("Default", Label::new("Multi-line\nText\nExample").into_any_element()),
408                            single_example("UI Label", Label::new("Compact\nUI\nLabel").line_height_style(LineHeightStyle::UiLabel).into_any_element()),
409                        ],
410                    ),
411                    example_group_with_title(
412                        "Special Cases",
413                        vec![
414                            single_example("Single Line", Label::new("Line 1\nLine 2\nLine 3").single_line().into_any_element()),
415                            single_example("Regular Truncation", div().max_w_24().child(Label::new("This is a very long file name that should be truncated: very_long_file_name_with_many_words.rs").truncate()).into_any_element()),
416                            single_example("Start Truncation", div().max_w_24().child(Label::new("zed/crates/ui/src/components/label/truncate/label/label.rs").truncate_start()).into_any_element()),
417                        ],
418                    ),
419                ])
420                .into_any_element()
421    }
422}
423
Served at tenant.openagents/omega Member data and write actions are omitted.