2019-07-04 23:05:17 +03:00
|
|
|
use std::{
|
|
|
|
|
fmt,
|
|
|
|
|
ops::{self, Bound},
|
|
|
|
|
};
|
2018-08-28 14:06:30 +03:00
|
|
|
|
2019-07-04 23:05:17 +03:00
|
|
|
use crate::{SmolStr, SyntaxElement, SyntaxNode, TextRange, TextUnit};
|
2018-08-28 14:06:30 +03:00
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2019-07-19 21:41:36 +03:00
|
|
|
pub struct SyntaxText {
|
|
|
|
|
node: SyntaxNode,
|
2018-08-28 14:06:30 +03:00
|
|
|
range: TextRange,
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl SyntaxText {
|
|
|
|
|
pub(crate) fn new(node: SyntaxNode) -> SyntaxText {
|
|
|
|
|
let range = node.range();
|
|
|
|
|
SyntaxText { node, range }
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2019-07-19 20:29:59 +03:00
|
|
|
pub fn try_fold_chunks<T, F, E>(&self, init: T, mut f: F) -> Result<T, E>
|
|
|
|
|
where
|
|
|
|
|
F: FnMut(T, &str) -> Result<T, E>,
|
|
|
|
|
{
|
|
|
|
|
self.node.descendants_with_tokens().try_fold(init, move |acc, element| {
|
|
|
|
|
let res = match element {
|
|
|
|
|
SyntaxElement::Token(token) => {
|
|
|
|
|
let range = match self.range.intersection(&token.range()) {
|
|
|
|
|
None => return Ok(acc),
|
|
|
|
|
Some(it) => it,
|
|
|
|
|
};
|
|
|
|
|
let slice = if range == token.range() {
|
|
|
|
|
token.text()
|
|
|
|
|
} else {
|
|
|
|
|
let range = range - token.range().start();
|
|
|
|
|
&token.text()[range]
|
|
|
|
|
};
|
|
|
|
|
f(acc, slice)?
|
|
|
|
|
}
|
|
|
|
|
SyntaxElement::Node(_) => acc,
|
|
|
|
|
};
|
|
|
|
|
Ok(res)
|
2018-10-15 17:44:23 -04:00
|
|
|
})
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2019-07-19 20:29:59 +03:00
|
|
|
pub fn try_for_each_chunk<F: FnMut(&str) -> Result<(), E>, E>(
|
|
|
|
|
&self,
|
|
|
|
|
mut f: F,
|
|
|
|
|
) -> Result<(), E> {
|
|
|
|
|
self.try_fold_chunks((), move |(), chunk| f(chunk))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn for_each_chunk<F: FnMut(&str)>(&self, mut f: F) {
|
|
|
|
|
enum Void {}
|
|
|
|
|
match self.try_for_each_chunk(|chunk| Ok::<(), Void>(f(chunk))) {
|
|
|
|
|
Ok(()) => (),
|
|
|
|
|
Err(void) => match void {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-28 22:58:02 +03:00
|
|
|
pub fn push_to(&self, buf: &mut String) {
|
2019-07-19 20:29:59 +03:00
|
|
|
self.for_each_chunk(|chunk| buf.push_str(chunk))
|
2018-08-28 22:58:02 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2018-08-28 14:06:30 +03:00
|
|
|
pub fn to_string(&self) -> String {
|
2019-07-18 19:23:05 +03:00
|
|
|
let mut buf = String::new();
|
|
|
|
|
self.push_to(&mut buf);
|
|
|
|
|
buf
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2019-05-25 12:56:52 +02:00
|
|
|
pub fn to_smol_string(&self) -> SmolStr {
|
2019-07-18 19:23:05 +03:00
|
|
|
self.to_string().into()
|
2019-05-25 12:56:52 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-28 14:06:30 +03:00
|
|
|
pub fn contains(&self, c: char) -> bool {
|
2019-07-19 20:29:59 +03:00
|
|
|
self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) }).is_err()
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2018-08-28 14:06:30 +03:00
|
|
|
pub fn find(&self, c: char) -> Option<TextUnit> {
|
|
|
|
|
let mut acc: TextUnit = 0.into();
|
2019-07-19 20:29:59 +03:00
|
|
|
let res = self.try_for_each_chunk(|chunk| {
|
2018-08-28 14:06:30 +03:00
|
|
|
if let Some(pos) = chunk.find(c) {
|
|
|
|
|
let pos: TextUnit = (pos as u32).into();
|
2019-07-19 20:29:59 +03:00
|
|
|
return Err(acc + pos);
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-07-19 20:29:59 +03:00
|
|
|
acc += TextUnit::of_str(chunk);
|
|
|
|
|
Ok(())
|
|
|
|
|
});
|
|
|
|
|
found(res)
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2018-08-28 14:06:30 +03:00
|
|
|
pub fn len(&self) -> TextUnit {
|
|
|
|
|
self.range.len()
|
|
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
2019-07-10 17:05:39 +02:00
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.range.is_empty()
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
pub fn slice(&self, range: impl ops::RangeBounds<TextUnit>) -> SyntaxText {
|
2019-05-15 19:07:49 +03:00
|
|
|
let start = match range.start_bound() {
|
2019-07-19 20:55:32 +03:00
|
|
|
Bound::Included(&b) => b,
|
|
|
|
|
Bound::Excluded(_) => panic!("utf-aware slicing can't work this way"),
|
|
|
|
|
Bound::Unbounded => 0.into(),
|
2019-05-15 19:07:49 +03:00
|
|
|
};
|
|
|
|
|
let end = match range.end_bound() {
|
2019-07-19 20:55:32 +03:00
|
|
|
Bound::Included(_) => panic!("utf-aware slicing can't work this way"),
|
|
|
|
|
Bound::Excluded(&b) => b,
|
|
|
|
|
Bound::Unbounded => self.len(),
|
2019-05-15 19:07:49 +03:00
|
|
|
};
|
2019-07-19 20:55:32 +03:00
|
|
|
assert!(start <= end);
|
|
|
|
|
let len = end - start;
|
|
|
|
|
let start = self.range.start() + start;
|
|
|
|
|
let end = start + len;
|
2019-05-15 19:07:49 +03:00
|
|
|
assert!(
|
|
|
|
|
start <= end,
|
|
|
|
|
"invalid slice, range: {:?}, slice: {:?}",
|
|
|
|
|
self.range,
|
|
|
|
|
(range.start_bound(), range.end_bound()),
|
|
|
|
|
);
|
|
|
|
|
let range = TextRange::from_to(start, end);
|
|
|
|
|
assert!(
|
|
|
|
|
range.is_subrange(&self.range),
|
|
|
|
|
"invalid slice, range: {:?}, slice: {:?}",
|
|
|
|
|
self.range,
|
|
|
|
|
range,
|
|
|
|
|
);
|
2019-07-19 21:41:36 +03:00
|
|
|
SyntaxText { node: self.node.clone(), range }
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
2019-01-10 13:54:58 +01:00
|
|
|
|
|
|
|
|
pub fn char_at(&self, offset: impl Into<TextUnit>) -> Option<char> {
|
|
|
|
|
let offset = offset.into();
|
2019-07-19 20:29:59 +03:00
|
|
|
let mut start: TextUnit = 0.into();
|
|
|
|
|
let res = self.try_for_each_chunk(|chunk| {
|
|
|
|
|
let end = start + TextUnit::of_str(chunk);
|
2018-08-31 14:52:29 +03:00
|
|
|
if start <= offset && offset < end {
|
|
|
|
|
let off: usize = u32::from(offset - start) as usize;
|
2019-07-19 20:29:59 +03:00
|
|
|
return Err(chunk[off..].chars().next().unwrap());
|
2018-08-31 14:52:29 +03:00
|
|
|
}
|
|
|
|
|
start = end;
|
2019-07-19 20:29:59 +03:00
|
|
|
Ok(())
|
|
|
|
|
});
|
|
|
|
|
found(res)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn found<T>(res: Result<(), T>) -> Option<T> {
|
|
|
|
|
match res {
|
|
|
|
|
Ok(()) => None,
|
|
|
|
|
Err(it) => Some(it),
|
2018-08-31 14:52:29 +03:00
|
|
|
}
|
2018-08-28 14:06:30 +03:00
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl fmt::Debug for SyntaxText {
|
2018-08-28 14:06:30 +03:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
fmt::Debug::fmt(&self.to_string(), f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl fmt::Display for SyntaxText {
|
2018-08-28 14:06:30 +03:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
fmt::Display::fmt(&self.to_string(), f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl From<SyntaxText> for String {
|
2019-01-03 18:59:17 +03:00
|
|
|
fn from(text: SyntaxText) -> String {
|
|
|
|
|
text.to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-05 15:28:07 +03:00
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl PartialEq<str> for SyntaxText {
|
2019-01-05 15:28:07 +03:00
|
|
|
fn eq(&self, mut rhs: &str) -> bool {
|
2019-07-19 20:29:59 +03:00
|
|
|
self.try_for_each_chunk(|chunk| {
|
|
|
|
|
if !rhs.starts_with(chunk) {
|
|
|
|
|
return Err(());
|
2019-01-05 15:28:07 +03:00
|
|
|
}
|
|
|
|
|
rhs = &rhs[chunk.len()..];
|
2019-07-19 20:29:59 +03:00
|
|
|
Ok(())
|
|
|
|
|
})
|
|
|
|
|
.is_ok()
|
2019-01-05 15:28:07 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 21:41:36 +03:00
|
|
|
impl PartialEq<&'_ str> for SyntaxText {
|
2019-01-05 15:28:07 +03:00
|
|
|
fn eq(&self, rhs: &&str) -> bool {
|
|
|
|
|
self == *rhs
|
|
|
|
|
}
|
|
|
|
|
}
|