Skip to repository content329 lines · 10.0 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:47:03.821Z 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
highlighted_label.rs
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