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, strikethrough: bool) -> Self {
50 self.base = self.base.strikethrough(strikethrough);
51 self
52 }
53
54 fn italic(mut self, italic: bool) -> Self {
55 self.base = self.base.italic(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, underline: bool) -> Self {
65 self.base = self.base.underline(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
80pub fn highlight_ranges(
81 text: &str,
82 indices: &[usize],
83 style: HighlightStyle,
84) -> Vec<(Range<usize>, HighlightStyle)> {
85 let mut highlight_indices = indices.iter().copied().peekable();
86 let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
87
88 while let Some(start_ix) = highlight_indices.next() {
89 let mut end_ix = start_ix;
90
91 loop {
92 end_ix = end_ix + text[end_ix..].chars().next().unwrap().len_utf8();
93 if let Some(&next_ix) = highlight_indices.peek() {
94 if next_ix == end_ix {
95 end_ix = next_ix;
96 highlight_indices.next();
97 continue;
98 }
99 }
100 break;
101 }
102
103 highlights.push((start_ix..end_ix, style));
104 }
105
106 highlights
107}
108
109impl RenderOnce for HighlightedLabel {
110 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
111 let highlight_color = cx.theme().colors().text_accent;
112
113 let highlights = highlight_ranges(
114 &self.label,
115 &self.highlight_indices,
116 HighlightStyle {
117 color: Some(highlight_color),
118 ..Default::default()
119 },
120 );
121
122 let mut text_style = cx.text_style();
123 text_style.color = self.base.color.color(cx);
124
125 self.base
126 .child(StyledText::new(self.label).with_highlights(&text_style, highlights))
127 }
128}