Skip to repository content423 lines · 13.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:52:53.172Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
label.rs
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