1mod get_word_tag;
2
3use crate::ll_line::{LLLine, LLToken, LToken, TextTag};
4use crate::type_bucket::AnyAttribute;
5use unicode_segmentation::UnicodeSegmentation;
6
7pub enum InputToken {
8 Text {
9 text: String,
11 attrs: Vec<AnyAttribute>,
13 },
14 Custom {
15 size: usize,
17 attrs: Vec<AnyAttribute>,
19 },
20}
21
22impl InputToken {
23 pub fn text(text: String, attrs: Vec<AnyAttribute>) -> Self {
24 InputToken::Text { text, attrs }
25 }
26
27 pub fn custom(size: usize, attrs: Vec<AnyAttribute>) -> Self {
28 InputToken::Custom { size, attrs }
29 }
30
31 pub fn add_attr<T: 'static + std::fmt::Debug>(&mut self, value: T) {
32 match self {
33 InputToken::Text { attrs, .. } => attrs.push(AnyAttribute::new(value)),
34 InputToken::Custom { attrs, .. } => attrs.push(AnyAttribute::new(value)),
35 }
36 }
37}
38
39#[deprecated = "Use create_line_from_input_tokens"]
41pub fn create_tokens<F>(input: Vec<InputToken>, get_text_size: F) -> LLLine
42where
43 F: Fn(&str) -> usize,
44{
45 create_line_from_input_tokens(input, get_text_size)
46}
47
48pub fn create_line_from_input_tokens<F>(input: Vec<InputToken>, get_text_size: F) -> LLLine
49where
50 F: Fn(&str) -> usize,
51{
52 let mut start_idx_end_idx_attributes: Vec<(usize, usize, Vec<AnyAttribute>)> = Vec::new();
53 let mut lltokens: Vec<LLToken> = Vec::new();
54 let mut current_size = 0;
55
56 for (ltokens, attrs) in input.into_iter().map(|input_token| match input_token {
57 InputToken::Text { text, attrs } => {
58 (create_tokens_for_string(&text, &get_text_size), attrs)
59 }
60 InputToken::Custom { size, attrs } => (vec![(LToken::Value, size)], attrs),
61 }) {
62 assert!(
63 !ltokens.is_empty(),
64 "Cannot create a LLLine from empty String"
65 );
66
67 let from_idx = lltokens.len();
68 for (ltoken, size) in ltokens {
69 let next_size = current_size + size;
70 lltokens.push(LLToken {
71 token_idx: lltokens.len(),
72 pos_starts_at: current_size,
73 pos_ends_at: next_size,
74 token: ltoken,
75 });
76
77 current_size = next_size;
78 }
79 let to_idx = lltokens.len() - 1;
80
81 start_idx_end_idx_attributes.push((from_idx, to_idx, attrs));
82 }
83
84 let mut ll_line = LLLine::new(lltokens);
85 for (start_idx, end_idx, attributes) in start_idx_end_idx_attributes {
86 ll_line.add_any_attrs(start_idx, end_idx, attributes);
87 }
88
89 ll_line
90}
91
92fn create_tokens_for_string<F>(input: &str, get_text_size: F) -> Vec<(LToken, usize)>
94where
95 F: Fn(&str) -> usize,
96{
97 input
99 .split_word_bounds()
100 .fold(Vec::new(), |mut ltokens, unicode_word| {
101 if unicode_word.chars().next().unwrap().is_ascii_digit() {
106 assert!(
108 unicode_word.is_ascii(),
109 "Unexpected non-ascii digit word boundary: {}",
110 unicode_word
111 );
112
113 let mut collected_digits = String::new();
114 let mut collected_letters = String::new();
115
116 macro_rules! insert_collected_digits {
119 () => {
120 if collected_digits.len() > 0 {
121 let size = get_text_size(&collected_digits);
122 ltokens.push((
123 LToken::Text(std::mem::take(&mut collected_digits), TextTag::NATN),
124 size,
125 ));
126 }
127 };
128 }
129
130 macro_rules! insert_collected_letters {
131 () => {
132 if collected_letters.len() > 0 {
133 let size = get_text_size(&collected_letters);
134 ltokens.push((
135 LToken::Text(std::mem::take(&mut collected_letters), TextTag::WORD),
136 size,
137 ));
138 }
139 };
140 }
141
142 for ch in unicode_word.chars() {
143 if ch.is_ascii_digit() {
144 insert_collected_letters!();
145 collected_digits.push(ch);
146 } else if ch.is_alphabetic() {
147 insert_collected_digits!();
148 collected_letters.push(ch);
149 } else {
150 insert_collected_letters!();
151 insert_collected_digits!();
152 let text = String::from(ch);
153 let size = get_text_size(&text);
154 ltokens.push((LToken::Text(text, TextTag::PUNC), size));
155 }
156 }
157
158 insert_collected_letters!();
159 insert_collected_digits!();
160 } else {
161 ltokens.push((
162 LToken::Text(
163 unicode_word.to_string(),
164 get_word_tag::get_unicode_word_tag(unicode_word),
165 ),
166 get_text_size(unicode_word),
167 ));
168 }
169
170 ltokens
171 })
172}
173
174#[cfg(test)]
175mod test {
176 use super::{create_tokens, InputToken};
177 use crate::ll_line::LLLineDisplay;
178 use crate::type_bucket::AnyAttribute;
179
180 #[derive(Debug, Clone)]
181 enum MarkKind {
182 Italic,
183 Bold,
184 }
185
186 #[derive(Debug, Clone)]
187 struct Link {
188 href: String,
189 }
190
191 #[test]
192 fn test_create_tokens() {
193 let ll_line = create_tokens(
194 vec![
195 InputToken::Text {
196 text: String::from("Hello, "),
197 attrs: Vec::new(),
198 },
199 InputToken::Text {
200 text: String::from("World"),
201 attrs: vec![
202 AnyAttribute::new(MarkKind::Bold),
203 AnyAttribute::new(MarkKind::Italic),
204 ],
205 },
206 InputToken::Text {
207 text: String::from("!"),
208 attrs: vec![],
209 },
210 ],
211 |text| text.len(),
212 );
213
214 let mut ll_line_display = LLLineDisplay::new(&ll_line);
215 ll_line_display.include::<MarkKind>();
216
217 insta::assert_display_snapshot!(ll_line_display, @r###"
218 Hello , World !
219 ╰───╯Italic
220 ╰───╯Bold
221 "###);
222 }
223
224 #[test]
225 fn test_create_tokens_email() {
226 let ll_line = create_tokens(
227 vec![InputToken::Text {
228 text: String::from("name@example.com"),
229 attrs: vec![
230 AnyAttribute::new(MarkKind::Italic),
231 AnyAttribute::new(Link {
232 href: String::from("mailto:name@example.com"),
233 }),
234 ],
235 }],
236 |text| text.len(),
237 );
238
239 let mut ll_line_display = LLLineDisplay::new(&ll_line);
241 ll_line_display.include::<MarkKind>();
242 ll_line_display.include::<Link>();
243
244 insta::assert_display_snapshot!(ll_line_display, @r###"
245 name @ example.com
246 ╰──────────────────╯Italic
247 ╰──────────────────╯Link { href: "mailto:name@example.com" }
248 "###);
249 }
250}