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