1use crate::{
2 cursor::{SyntaxNode, SyntaxToken},
3 TextRange, TextSize, TokenAtOffset,
4};
5use biome_text_size::TextLen;
6use std::iter::FusedIterator;
7use std::{cmp::Ordering, fmt};
8
9#[derive(Clone)]
10pub struct SyntaxNodeText {
11 node: SyntaxNode,
12 range: TextRange,
13}
14
15impl SyntaxNodeText {
16 pub(crate) fn new(node: SyntaxNode) -> SyntaxNodeText {
17 let range = node.text_range();
18 SyntaxNodeText { node, range }
19 }
20
21 pub(crate) fn with_range(node: SyntaxNode, range: TextRange) -> SyntaxNodeText {
22 SyntaxNodeText { node, range }
23 }
24
25 pub fn len(&self) -> TextSize {
26 self.range.len()
27 }
28
29 pub fn is_empty(&self) -> bool {
30 self.range.is_empty()
31 }
32
33 pub fn contains_char(&self, c: char) -> bool {
34 self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) })
35 .is_err()
36 }
37
38 pub fn find_char(&self, c: char) -> Option<TextSize> {
39 let mut acc: TextSize = 0.into();
40 let res = self.try_for_each_chunk(|chunk| {
41 if let Some(pos) = chunk.find(c) {
42 let pos: TextSize = (pos as u32).into();
43 return Err(acc + pos);
44 }
45 acc += TextSize::of(chunk);
46 Ok(())
47 });
48 found(res)
49 }
50
51 pub fn char_at(&self, offset: TextSize) -> Option<char> {
52 let mut start: TextSize = 0.into();
53 let res = self.try_for_each_chunk(|chunk| {
54 let end = start + TextSize::of(chunk);
55 if start <= offset && offset < end {
56 let off: usize = u32::from(offset - start) as usize;
57 return Err(chunk[off..].chars().next().unwrap());
58 }
59 start = end;
60 Ok(())
61 });
62 found(res)
63 }
64
65 pub fn slice<R: private::SyntaxTextRange>(&self, range: R) -> SyntaxNodeText {
66 let start = range.start().unwrap_or_default();
67 let end = range.end().unwrap_or_else(|| self.len());
68 assert!(start <= end);
69 let len = end - start;
70 let start = self.range.start() + start;
71 let end = start + len;
72 assert!(
73 start <= end,
74 "invalid slice, range: {:?}, slice: {:?}",
75 self.range,
76 (range.start(), range.end()),
77 );
78 let range = TextRange::new(start, end);
79 assert!(
80 self.range.contains_range(range),
81 "invalid slice, range: {:?}, slice: {:?}",
82 self.range,
83 range,
84 );
85 SyntaxNodeText {
86 node: self.node.clone(),
87 range,
88 }
89 }
90
91 pub fn starts_with(&self, mut prefix: &str) -> bool {
92 for (token, range) in self.tokens_with_ranges() {
93 if prefix.is_empty() {
94 return true;
95 }
96
97 let text = &token.text()[range];
98 match text.len().cmp(&prefix.len()) {
99 Ordering::Equal => return text == prefix,
100 Ordering::Greater => return text.starts_with(prefix),
101 Ordering::Less => {
102 if text == &prefix[..text.len()] {
103 prefix = &prefix[text.len()..];
104 } else {
105 return false;
106 }
107 }
108 }
109 }
110
111 prefix.is_empty()
112 }
113
114 pub fn try_fold_chunks<T, F, E>(&self, init: T, mut f: F) -> Result<T, E>
115 where
116 F: FnMut(T, &str) -> Result<T, E>,
117 {
118 self.tokens_with_ranges()
119 .try_fold(init, move |acc, (token, range)| {
120 f(acc, &token.text()[range])
121 })
122 }
123
124 pub fn try_for_each_chunk<F: FnMut(&str) -> Result<(), E>, E>(
125 &self,
126 mut f: F,
127 ) -> Result<(), E> {
128 self.try_fold_chunks((), move |(), chunk| f(chunk))
129 }
130
131 pub fn for_each_chunk<F: FnMut(&str)>(&self, mut f: F) {
132 enum Void {}
133 let out = self.try_for_each_chunk(|chunk| {
134 f(chunk);
135 Ok::<(), Void>(())
136 });
137 match out {
138 Ok(()) => (),
139 Err(void) => match void {},
140 }
141 }
142
143 fn tokens_with_ranges(&self) -> impl FusedIterator<Item = (SyntaxToken, TextRange)> {
144 SyntaxNodeTokenWithRanges::new(self)
145 }
146
147 pub fn chars(&self) -> impl FusedIterator<Item = char> {
148 SyntaxNodeTextChars::new(self)
149 }
150}
151
152#[derive(Clone)]
153struct SyntaxNodeTokenWithRanges {
154 text_range: TextRange,
155 next_token: Option<(SyntaxToken, TextRange)>,
156}
157
158impl SyntaxNodeTokenWithRanges {
159 fn new(text: &SyntaxNodeText) -> Self {
160 let text_range = text.range;
161
162 let token = match text.node.token_at_offset(text_range.start()) {
163 TokenAtOffset::None => None,
164 TokenAtOffset::Single(token) => Some(token),
165 TokenAtOffset::Between(_, next) => Some(next),
166 };
167
168 Self {
169 next_token: token.and_then(|token| Self::with_intersecting_range(token, text_range)),
170 text_range,
171 }
172 }
173
174 fn with_intersecting_range(
175 token: SyntaxToken,
176 text_range: TextRange,
177 ) -> Option<(SyntaxToken, TextRange)> {
178 let token_range = token.text_range();
179
180 let range = text_range.intersect(token_range)?;
181 Some((token, range - token_range.start()))
182 }
183}
184
185impl Iterator for SyntaxNodeTokenWithRanges {
186 type Item = (SyntaxToken, TextRange);
187
188 fn next(&mut self) -> Option<Self::Item> {
189 let (token, range) = self.next_token.take()?;
190
191 self.next_token = token
192 .next_token()
193 .and_then(|token| Self::with_intersecting_range(token, self.text_range));
194
195 Some((token, range))
196 }
197}
198
199impl FusedIterator for SyntaxNodeTokenWithRanges {}
200
201#[derive(Clone)]
202struct SyntaxNodeTextChars {
203 head: Option<(SyntaxToken, TextRange)>,
204 tail: SyntaxNodeTokenWithRanges,
205 index: TextSize,
206}
207
208impl SyntaxNodeTextChars {
209 fn new(text: &SyntaxNodeText) -> Self {
210 let mut chunks = SyntaxNodeTokenWithRanges::new(text);
211
212 Self {
213 head: chunks.next(),
214 tail: chunks,
215 index: TextSize::default(),
216 }
217 }
218}
219
220impl Iterator for SyntaxNodeTextChars {
221 type Item = char;
222
223 fn next(&mut self) -> Option<Self::Item> {
224 loop {
225 let (token, range) = self.head.as_ref()?;
226
227 if self.index >= range.end() {
228 self.head = self.tail.next();
229 self.index = TextSize::default();
230 continue;
231 }
232
233 let text = token.text();
234
235 let next_char = text[TextRange::new(self.index, range.end())]
237 .chars()
238 .next()
239 .unwrap();
240
241 self.index += next_char.text_len();
242 break Some(next_char);
243 }
244 }
245}
246
247impl FusedIterator for SyntaxNodeTextChars {}
248
249fn found<T>(res: Result<(), T>) -> Option<T> {
250 match res {
251 Ok(()) => None,
252 Err(it) => Some(it),
253 }
254}
255
256impl fmt::Debug for SyntaxNodeText {
257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258 fmt::Debug::fmt(&self.to_string(), f)
259 }
260}
261
262impl fmt::Display for SyntaxNodeText {
263 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264 self.try_for_each_chunk(|chunk| fmt::Display::fmt(chunk, f))
265 }
266}
267
268impl From<SyntaxNodeText> for String {
269 fn from(text: SyntaxNodeText) -> String {
270 text.to_string()
271 }
272}
273
274impl PartialEq<str> for SyntaxNodeText {
275 fn eq(&self, mut rhs: &str) -> bool {
276 self.try_for_each_chunk(|chunk| {
277 if !rhs.starts_with(chunk) {
278 return Err(());
279 }
280 rhs = &rhs[chunk.len()..];
281 Ok(())
282 })
283 .is_ok()
284 && rhs.is_empty()
285 }
286}
287
288impl PartialEq<SyntaxNodeText> for str {
289 fn eq(&self, rhs: &SyntaxNodeText) -> bool {
290 rhs == self
291 }
292}
293
294impl PartialEq<&'_ str> for SyntaxNodeText {
295 fn eq(&self, rhs: &&str) -> bool {
296 self == *rhs
297 }
298}
299
300impl PartialEq<SyntaxNodeText> for &'_ str {
301 fn eq(&self, rhs: &SyntaxNodeText) -> bool {
302 rhs == self
303 }
304}
305
306impl PartialEq for SyntaxNodeText {
307 fn eq(&self, other: &SyntaxNodeText) -> bool {
308 if self.range.len() != other.range.len() {
309 return false;
310 }
311 let mut lhs = self.tokens_with_ranges();
312 let mut rhs = other.tokens_with_ranges();
313 zip_texts(&mut lhs, &mut rhs).is_none()
314 && lhs.all(|it| it.1.is_empty())
315 && rhs.all(|it| it.1.is_empty())
316 }
317}
318
319fn zip_texts<I: Iterator<Item = (SyntaxToken, TextRange)>>(xs: &mut I, ys: &mut I) -> Option<()> {
320 let mut x = xs.next()?;
321 let mut y = ys.next()?;
322 loop {
323 while x.1.is_empty() {
324 x = xs.next()?;
325 }
326 while y.1.is_empty() {
327 y = ys.next()?;
328 }
329 let x_text = &x.0.text()[x.1];
330 let y_text = &y.0.text()[y.1];
331 if !(x_text.starts_with(y_text) || y_text.starts_with(x_text)) {
332 return Some(());
333 }
334 let advance = std::cmp::min(x.1.len(), y.1.len());
335 x.1 = TextRange::new(x.1.start() + advance, x.1.end());
336 y.1 = TextRange::new(y.1.start() + advance, y.1.end());
337 }
338}
339
340impl Eq for SyntaxNodeText {}
341
342mod private {
343 use std::ops;
344
345 use crate::{TextRange, TextSize};
346
347 pub trait SyntaxTextRange {
348 fn start(&self) -> Option<TextSize>;
349 fn end(&self) -> Option<TextSize>;
350 }
351
352 impl SyntaxTextRange for TextRange {
353 fn start(&self) -> Option<TextSize> {
354 Some(TextRange::start(*self))
355 }
356 fn end(&self) -> Option<TextSize> {
357 Some(TextRange::end(*self))
358 }
359 }
360
361 impl SyntaxTextRange for ops::Range<TextSize> {
362 fn start(&self) -> Option<TextSize> {
363 Some(self.start)
364 }
365 fn end(&self) -> Option<TextSize> {
366 Some(self.end)
367 }
368 }
369
370 impl SyntaxTextRange for ops::RangeFrom<TextSize> {
371 fn start(&self) -> Option<TextSize> {
372 Some(self.start)
373 }
374 fn end(&self) -> Option<TextSize> {
375 None
376 }
377 }
378
379 impl SyntaxTextRange for ops::RangeTo<TextSize> {
380 fn start(&self) -> Option<TextSize> {
381 None
382 }
383 fn end(&self) -> Option<TextSize> {
384 Some(self.end)
385 }
386 }
387
388 impl SyntaxTextRange for ops::RangeFull {
389 fn start(&self) -> Option<TextSize> {
390 None
391 }
392 fn end(&self) -> Option<TextSize> {
393 None
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use crate::raw_language::{RawLanguage, RawLanguageKind, RawSyntaxTreeBuilder};
401 use crate::SyntaxNode;
402
403 fn build_tree(chunks: &[&str]) -> SyntaxNode<RawLanguage> {
404 let mut builder = RawSyntaxTreeBuilder::new();
405 builder.start_node(RawLanguageKind::ROOT);
406 for &chunk in chunks.iter() {
407 builder.token(RawLanguageKind::STRING_TOKEN, chunk);
408 }
409 builder.finish_node();
410 builder.finish()
411 }
412
413 #[test]
414 fn test_text_equality() {
415 fn do_check(t1: &[&str], t2: &[&str]) {
416 let t1 = build_tree(t1).text();
417 let t2 = build_tree(t2).text();
418 let expected = t1.to_string() == t2.to_string();
419 let actual = t1 == t2;
420 assert_eq!(expected, actual, "`{t1}` (SyntaxText) `{t2}` (SyntaxText)");
421 let actual = t1 == *t2.to_string();
422 assert_eq!(expected, actual, "`{t1}` (SyntaxText) `{t2}` (&str)");
423 }
424 fn check(t1: &[&str], t2: &[&str]) {
425 do_check(t1, t2);
426 do_check(t2, t1)
427 }
428
429 check(&[""], &[""]);
430 check(&["a"], &[""]);
431 check(&["a"], &["a"]);
432 check(&["abc"], &["def"]);
433 check(&["hello", "world"], &["hello", "world"]);
434 check(&["hellowo", "rld"], &["hell", "oworld"]);
435 check(&["hel", "lowo", "rld"], &["helloworld"]);
436 check(&["{", "abc", "}"], &["{", "123", "}"]);
437 check(&["{", "abc", "}", "{"], &["{", "123", "}"]);
438 check(&["{", "abc", "}"], &["{", "123", "}", "{"]);
439 check(&["{", "abc", "}ab"], &["{", "abc", "}", "ab"]);
440 }
441
442 #[test]
443 fn test_chars() {
444 fn check(t1: &[&str], expected: &str) {
445 let t1 = build_tree(t1).text();
446 let actual = t1.chars().collect::<String>();
447
448 assert_eq!(
449 expected, &actual,
450 "`{actual}` (SyntaxText) `{expected}` (SyntaxText)"
451 );
452 }
453
454 check(&[""], "");
455 check(&["a"], "a");
456 check(&["hello", "world"], "helloworld");
457 check(&["hellowo", "rld"], "helloworld");
458 check(&["hel", "lowo", "rld"], "helloworld");
459 check(&["{", "abc", "}"], "{abc}");
460 check(&["{", "abc", "}", "{"], "{abc}{");
461 check(&["{", "abc", "}ab"], "{abc}ab");
462 }
463}