use rowan::{TextRange, TextSize};
use crate::dcf;
pub struct Folded {
pub text: String,
pub range: TextRange,
lines: Vec<(usize, TextSize, usize)>,
}
impl Folded {
pub fn map(&self, range: TextRange) -> TextRange {
let start = self.offset(range.start().into());
let end = self.offset(range.end().into());
TextRange::new(start, end.max(start))
}
fn offset(&self, at: usize) -> TextSize {
for &(folded, source, len) in &self.lines {
if at <= folded + len {
return source + TextSize::from(at.saturating_sub(folded) as u32);
}
}
self.range.end()
}
}
pub fn value(field: &dcf::Field) -> Option<(String, TextRange)> {
let folded = folded(field)?;
Some((folded.text, folded.range))
}
pub fn folded(field: &dcf::Field) -> Option<Folded> {
let lines: Vec<dcf::ValueLine> = field
.value_lines()
.filter(|line| !line.trimmed_text().is_empty())
.collect();
let (first, last) = (lines.first()?, lines.last()?);
let mut text = String::new();
let mut spans = Vec::with_capacity(lines.len());
for line in &lines {
if !text.is_empty() {
text.push('\n');
}
let content = line.trimmed_text();
spans.push((text.len(), trimmed_start(line), content.len()));
text.push_str(&content);
}
Some(Folded {
text,
range: TextRange::new(trimmed_start(first), trimmed_end(last)),
lines: spans,
})
}
fn trimmed_start(line: &dcf::ValueLine) -> TextSize {
let range = line.content_range();
match line.content() {
Some(tok) => {
let lead = tok.text().len() - tok.text().trim_start().len();
range.start() + TextSize::from(lead as u32)
}
None => range.start(),
}
}
fn trimmed_end(line: &dcf::ValueLine) -> TextSize {
let range = line.content_range();
match line.content() {
Some(tok) => {
let trail = tok.text().len() - tok.text().trim_end().len();
range.end() - TextSize::from(trail as u32)
}
None => range.end(),
}
}
pub fn escape(text: &str) -> String {
text.replace('\n', "\\n")
}