use std::{fmt, ops}; use ra_text_edit::text_utils::contains_offset_nonstrict; use crate::{ text_utils::intersect, SyntaxNodeRef, TextRange, TextUnit, }; #[derive(Clone)] pub struct SyntaxText<'a> { node: SyntaxNodeRef<'a>, range: TextRange, } impl<'a> SyntaxText<'a> { pub(crate) fn new(node: SyntaxNodeRef<'a>) -> SyntaxText<'a> { SyntaxText { node, range: node.range(), } } pub fn chunks(&self) -> impl Iterator<Item = &'a str> { let range = self.range; self.node.descendants().filter_map(move |node| { let text = node.leaf_text()?; let range = intersect(range, node.range())?; let range = range - node.range().start(); Some(&text[range]) }) } pub fn push_to(&self, buf: &mut String) { self.chunks().for_each(|it| buf.push_str(it)); } pub fn to_string(&self) -> String { self.chunks().collect() } pub fn contains(&self, c: char) -> bool { self.chunks().any(|it| it.contains(c)) } pub fn find(&self, c: char) -> Option<TextUnit> { let mut acc: TextUnit = 0.into(); for chunk in self.chunks() { if let Some(pos) = chunk.find(c) { let pos: TextUnit = (pos as u32).into(); return Some(acc + pos); } acc += TextUnit::of_str(chunk); } None } pub fn len(&self) -> TextUnit { self.range.len() } pub fn slice(&self, range: impl SyntaxTextSlice) -> SyntaxText<'a> { let range = range.restrict(self.range).unwrap_or_else(|| { panic!("invalid slice, range: {:?}, slice: {:?}", self.range, range) }); SyntaxText { node: self.node, range, } } pub fn char_at(&self, offset: TextUnit) -> Option<char> { let mut start: TextUnit = 0.into(); for chunk in self.chunks() { let end = start + TextUnit::of_str(chunk); if start <= offset && offset < end { let off: usize = u32::from(offset - start) as usize; return Some(chunk[off..].chars().next().unwrap()); } start = end; } None } } impl<'a> fmt::Debug for SyntaxText<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.to_string(), f) } } impl<'a> fmt::Display for SyntaxText<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.to_string(), f) } } pub trait SyntaxTextSlice: fmt::Debug { fn restrict(&self, range: TextRange) -> Option<TextRange>; } impl SyntaxTextSlice for TextRange { fn restrict(&self, range: TextRange) -> Option<TextRange> { intersect(*self, range) } } impl SyntaxTextSlice for ops::RangeTo<TextUnit> { fn restrict(&self, range: TextRange) -> Option<TextRange> { if !contains_offset_nonstrict(range, self.end) { return None; } Some(TextRange::from_to(range.start(), self.end)) } } impl SyntaxTextSlice for ops::RangeFrom<TextUnit> { fn restrict(&self, range: TextRange) -> Option<TextRange> { if !contains_offset_nonstrict(range, self.start) { return None; } Some(TextRange::from_to(self.start, range.end())) } } impl SyntaxTextSlice for ops::Range<TextUnit> { fn restrict(&self, range: TextRange) -> Option<TextRange> { TextRange::from_to(self.start, self.end).restrict(range) } }