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