1#![allow(missing_docs)]
2
3use std::ops::Range;
4
5use gpui::{FontWeight, HighlightStyle, StyledText};
6
7use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
8
9#[derive(IntoElement)]
10pub struct HighlightedLabel {
11 base: LabelLike,
12 label: SharedString,
13 highlight_indices: Vec<usize>,
14}
15
16impl HighlightedLabel {
17 /// Constructs a label with the given characters highlighted.
18 /// Characters are identified by UTF-8 byte position.
19 pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
20 Self {
21 base: LabelLike::new(),
22 label: label.into(),
23 highlight_indices,
24 }
25 }
26}
27
28impl LabelCommon for HighlightedLabel {
29 fn size(mut self, size: LabelSize) -> Self {
30 self.base = self.base.size(size);
31 self
32 }
33
34 fn weight(mut self, weight: FontWeight) -> Self {
35 self.base = self.base.weight(weight);
36 self
37 }
38
39 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
40 self.base = self.base.line_height_style(line_height_style);
41 self
42 }
43
44 fn color(mut self, color: Color) -> Self {
45 self.base = self.base.color(color);
46 self
47 }
48
49 fn strikethrough(mut self) -> Self {
50 self.base = self.base.strikethrough();
51 self
52 }
53
54 fn italic(mut self) -> Self {
55 self.base = self.base.italic();
56 self
57 }
58
59 fn alpha(mut self, alpha: f32) -> Self {
60 self.base = self.base.alpha(alpha);
61 self
62 }
63
64 fn underline(mut self) -> Self {
65 self.base = self.base.underline();
66 self
67 }
68
69 fn text_ellipsis(mut self) -> Self {
70 self.base = self.base.text_ellipsis();
71 self
72 }
73
74 fn single_line(mut self) -> Self {
75 self.base = self.base.single_line();
76 self
77 }
78
79 fn buffer_font(mut self, cx: &App) -> Self {
80 self.base = self.base.buffer_font(cx);
81 self
82 }
83}
84
85pub fn highlight_ranges(
86 text: &str,
87 indices: &[usize],
88 style: HighlightStyle,
89) -> Vec<(Range<usize>, HighlightStyle)> {
90 let mut highlight_indices = indices.iter().copied().peekable();
91 let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
92
93 while let Some(start_ix) = highlight_indices.next() {
94 let mut end_ix = start_ix;
95
96 loop {
97 end_ix = end_ix + text[end_ix..].chars().next().unwrap().len_utf8();
98 if let Some(&next_ix) = highlight_indices.peek() {
99 if next_ix == end_ix {
100 end_ix = next_ix;
101 highlight_indices.next();
102 continue;
103 }
104 }
105 break;
106 }
107
108 highlights.push((start_ix..end_ix, style));
109 }
110
111 highlights
112}
113
114impl RenderOnce for HighlightedLabel {
115 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
116 let highlight_color = cx.theme().colors().text_accent;
117
118 let highlights = highlight_ranges(
119 &self.label,
120 &self.highlight_indices,
121 HighlightStyle {
122 color: Some(highlight_color),
123 ..Default::default()
124 },
125 );
126
127 let mut text_style = window.text_style();
128 text_style.color = self.base.color.color(cx);
129
130 self.base
131 .child(StyledText::new(self.label).with_highlights(&text_style, highlights))
132 }
133}