Files
rust/crates/ra_syntax/src/syntax_text.rs

189 lines
5.2 KiB
Rust
Raw Normal View History

use std::{
fmt,
ops::{self, Bound},
};
2018-08-28 14:06:30 +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
}
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-08-28 14:06:30 +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) {
self.for_each_chunk(|chunk| buf.push_str(chunk))
2018-08-28 22:58:02 +03: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
}
pub fn to_smol_string(&self) -> SmolStr {
2019-07-18 19:23:05 +03:00
self.to_string().into()
}
2018-08-28 14:06:30 +03:00
pub fn contains(&self, c: char) -> bool {
self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) }).is_err()
2018-08-28 14:06:30 +03:00
}
2018-08-28 14:06:30 +03:00
pub fn find(&self, c: char) -> Option<TextUnit> {
let mut acc: TextUnit = 0.into();
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();
return Err(acc + pos);
2018-08-28 14:06:30 +03:00
}
acc += TextUnit::of_str(chunk);
Ok(())
});
found(res)
2018-08-28 14:06:30 +03:00
}
2018-08-28 14:06:30 +03:00
pub fn len(&self) -> TextUnit {
self.range.len()
}
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
}
pub fn char_at(&self, offset: impl Into<TextUnit>) -> Option<char> {
let offset = offset.into();
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;
return Err(chunk[off..].chars().next().unwrap());
2018-08-31 14:52:29 +03:00
}
start = end;
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-07-19 21:41:36 +03:00
impl PartialEq<str> 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()
}
}
2019-07-19 21:41:36 +03:00
impl PartialEq<&'_ str> for SyntaxText {
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}