use std::fmt; use crate::{ cursor::{SyntaxNode, SyntaxToken}, TextRange, TextSize, }; #[derive(Clone)] pub struct SyntaxText { node: SyntaxNode, range: TextRange, } impl SyntaxText { pub(crate) fn new(node: SyntaxNode) -> SyntaxText { let range = node.text_range(); SyntaxText { node, range } } pub fn len(&self) -> TextSize { self.range.len() } pub fn is_empty(&self) -> bool { self.range.is_empty() } pub fn contains_char(&self, c: char) -> bool { self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) }).is_err() } pub fn find_char(&self, c: char) -> Option { let mut acc: TextSize = 0.into(); let res = self.try_for_each_chunk(|chunk| { if let Some(pos) = chunk.find(c) { let pos: TextSize = (pos as u32).into(); return Err(acc + pos); } acc += TextSize::of(chunk); Ok(()) }); found(res) } pub fn char_at(&self, offset: TextSize) -> Option { let offset = offset.into(); let mut start: TextSize = 0.into(); let res = self.try_for_each_chunk(|chunk| { let end = start + TextSize::of(chunk); if start <= offset && offset < end { let off: usize = u32::from(offset - start) as usize; return Err(chunk[off..].chars().next().unwrap()); } start = end; Ok(()) }); found(res) } pub fn slice(&self, range: R) -> SyntaxText { let start = range.start().unwrap_or_default(); let end = range.end().unwrap_or(self.len()); assert!(start <= end); let len = end - start; let start = self.range.start() + start; let end = start + len; assert!( start <= end, "invalid slice, range: {:?}, slice: {:?}", self.range, (range.start(), range.end()), ); let range = TextRange::new(start, end); assert!( self.range.contains_range(range), "invalid slice, range: {:?}, slice: {:?}", self.range, range, ); SyntaxText { node: self.node.clone(), range } } pub fn try_fold_chunks(&self, init: T, mut f: F) -> Result where F: FnMut(T, &str) -> Result, { self.tokens_with_ranges() .try_fold(init, move |acc, (token, range)| f(acc, &token.text()[range])) } pub fn try_for_each_chunk Result<(), E>, E>( &self, mut f: F, ) -> Result<(), E> { self.try_fold_chunks((), move |(), chunk| f(chunk)) } pub fn for_each_chunk(&self, mut f: F) { enum Void {} match self.try_for_each_chunk(|chunk| Ok::<(), Void>(f(chunk))) { Ok(()) => (), Err(void) => match void {}, } } fn tokens_with_ranges(&self) -> impl Iterator { let text_range = self.range; self.node.descendants_with_tokens().filter_map(|element| element.into_token()).filter_map( move |token| { let token_range = token.text_range(); let range = text_range.intersect(token_range)?; Some((token, range - token_range.start())) }, ) } } fn found(res: Result<(), T>) -> Option { match res { Ok(()) => None, Err(it) => Some(it), } } impl fmt::Debug for SyntaxText { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.to_string(), f) } } impl fmt::Display for SyntaxText { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.try_for_each_chunk(|chunk| fmt::Display::fmt(chunk, f)) } } impl From for String { fn from(text: SyntaxText) -> String { text.to_string() } } impl PartialEq for SyntaxText { fn eq(&self, mut rhs: &str) -> bool { self.try_for_each_chunk(|chunk| { if !rhs.starts_with(chunk) { return Err(()); } rhs = &rhs[chunk.len()..]; Ok(()) }) .is_ok() && rhs.is_empty() } } impl PartialEq for str { fn eq(&self, rhs: &SyntaxText) -> bool { rhs == self } } impl PartialEq<&'_ str> for SyntaxText { fn eq(&self, rhs: &&str) -> bool { self == *rhs } } impl PartialEq for &'_ str { fn eq(&self, rhs: &SyntaxText) -> bool { rhs == self } } impl PartialEq for SyntaxText { fn eq(&self, other: &SyntaxText) -> bool { if self.range.len() != other.range.len() { return false; } let mut lhs = self.tokens_with_ranges(); let mut rhs = other.tokens_with_ranges(); zip_texts(&mut lhs, &mut rhs).is_none() && lhs.all(|it| it.1.is_empty()) && rhs.all(|it| it.1.is_empty()) } } fn zip_texts>(xs: &mut I, ys: &mut I) -> Option<()> { let mut x = xs.next()?; let mut y = ys.next()?; loop { while x.1.is_empty() { x = xs.next()?; } while y.1.is_empty() { y = ys.next()?; } let x_text = &x.0.text()[x.1]; let y_text = &y.0.text()[y.1]; if !(x_text.starts_with(y_text) || y_text.starts_with(x_text)) { return Some(()); } let advance = std::cmp::min(x.1.len(), y.1.len()); x.1 = TextRange::new(x.1.start() + advance, x.1.end()); y.1 = TextRange::new(y.1.start() + advance, y.1.end()); } } impl Eq for SyntaxText {} mod private { use std::ops; use crate::{TextRange, TextSize}; pub trait SyntaxTextRange { fn start(&self) -> Option; fn end(&self) -> Option; } impl SyntaxTextRange for TextRange { fn start(&self) -> Option { Some(TextRange::start(*self)) } fn end(&self) -> Option { Some(TextRange::end(*self)) } } impl SyntaxTextRange for ops::Range { fn start(&self) -> Option { Some(self.start) } fn end(&self) -> Option { Some(self.end) } } impl SyntaxTextRange for ops::RangeFrom { fn start(&self) -> Option { Some(self.start) } fn end(&self) -> Option { None } } impl SyntaxTextRange for ops::RangeTo { fn start(&self) -> Option { None } fn end(&self) -> Option { Some(self.end) } } impl SyntaxTextRange for ops::RangeFull { fn start(&self) -> Option { None } fn end(&self) -> Option { None } } } #[cfg(test)] mod tests { use crate::{green::SyntaxKind, GreenNodeBuilder}; use super::*; fn build_tree(chunks: &[&str]) -> SyntaxNode { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind(62)); for &chunk in chunks.iter() { builder.token(SyntaxKind(92), chunk.into()) } builder.finish_node(); SyntaxNode::new_root(builder.finish()) } #[test] fn test_text_equality() { fn do_check(t1: &[&str], t2: &[&str]) { let t1 = build_tree(t1).text(); let t2 = build_tree(t2).text(); let expected = t1.to_string() == t2.to_string(); let actual = t1 == t2; assert_eq!(expected, actual, "`{}` (SyntaxText) `{}` (SyntaxText)", t1, t2); let actual = t1 == &*t2.to_string(); assert_eq!(expected, actual, "`{}` (SyntaxText) `{}` (&str)", t1, t2); } fn check(t1: &[&str], t2: &[&str]) { do_check(t1, t2); do_check(t2, t1) } check(&[""], &[""]); check(&["a"], &[""]); check(&["a"], &["a"]); check(&["abc"], &["def"]); check(&["hello", "world"], &["hello", "world"]); check(&["hellowo", "rld"], &["hell", "oworld"]); check(&["hel", "lowo", "rld"], &["helloworld"]); check(&["{", "abc", "}"], &["{", "123", "}"]); check(&["{", "abc", "}", "{"], &["{", "123", "}"]); check(&["{", "abc", "}"], &["{", "123", "}", "{"]); check(&["{", "abc", "}ab"], &["{", "abc", "}", "ab"]); } }