1use crate::Color;
2use std::sync::Arc;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct SpanStyle {
6 pub color: Option<Color>,
7 pub font_size: Option<f32>,
8}
9
10impl SpanStyle {
11 pub const fn default() -> Self {
12 Self {
13 color: None,
14 font_size: None,
15 }
16 }
17
18 pub fn color(mut self, c: Color) -> Self {
19 self.color = Some(c);
20 self
21 }
22
23 pub fn font_size(mut self, px: f32) -> Self {
24 self.font_size = Some(px);
25 self
26 }
27}
28
29impl Default for SpanStyle {
30 fn default() -> Self {
31 Self::default()
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct TextSpan {
38 pub start: usize,
40 pub end: usize,
42 pub style: SpanStyle,
43}
44
45#[derive(Debug, Clone)]
49pub struct AnnotatedString {
50 pub text: String,
51 pub spans: Arc<[TextSpan]>,
52}
53
54impl AnnotatedString {
55 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
56 let text = text.into();
57 Self {
58 text,
59 spans: spans.into(),
60 }
61 }
62
63 pub fn as_str(&self) -> &str {
64 &self.text
65 }
66}
67
68impl From<String> for AnnotatedString {
69 fn from(text: String) -> Self {
70 Self {
71 text,
72 spans: Arc::from([]),
73 }
74 }
75}
76
77impl From<&str> for AnnotatedString {
78 fn from(text: &str) -> Self {
79 Self {
80 text: text.to_string(),
81 spans: Arc::from([]),
82 }
83 }
84}
85
86#[derive(Default)]
88pub struct AnnotatedStringBuilder {
89 text: String,
90 spans: Vec<TextSpan>,
91}
92
93impl AnnotatedStringBuilder {
94 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn push(&mut self, text: &str) -> &mut Self {
100 self.text.push_str(text);
101 self
102 }
103
104 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
106 let start = self.text.len();
107 self.text.push_str(text);
108 let end = self.text.len();
109 if start < end {
110 self.spans.push(TextSpan { start, end, style });
111 }
112 self
113 }
114
115 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
117 self.push_with_style(text, SpanStyle::default().color(color))
118 }
119
120 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
122 if start < end && end <= self.text.len() {
123 self.spans.push(TextSpan { start, end, style });
124 }
125 self
126 }
127
128 pub fn build(&mut self) -> AnnotatedString {
129 let text = std::mem::take(&mut self.text);
130 self.spans.sort_by_key(|s| s.start);
131 let mut merged: Vec<TextSpan> = Vec::new();
133 for span in std::mem::take(&mut self.spans) {
134 if let Some(last) = merged.last_mut() {
135 if last.end == span.start && last.style == span.style {
136 last.end = span.end;
137 continue;
138 }
139 }
140 merged.push(span);
141 }
142 AnnotatedString {
143 text,
144 spans: merged.into(),
145 }
146 }
147}
148
149pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
151 let mut builder = AnnotatedStringBuilder::new();
152 b(&mut builder);
153 builder.build()
154}