use crate::syntax::SyntaxElement;
use crate::syntax::SyntaxKind;
use crate::syntax::SyntaxNode;
use crate::syntax::SyntaxToken;
use crate::text::TextRange;
use crate::text::TextSize;
pub(crate) fn attach_trivia(root: &mut SyntaxNode, source: &str) {
let src_end = TextSize::new(source.len() as u32);
if root.range().end() < src_end {
root.extend_range_to(src_end);
}
attach(root, source);
}
fn attach(node: &mut SyntaxNode, source: &str) {
let mut old = node.take_children();
for child in &mut old {
if let SyntaxElement::Node(n) = child {
attach(n, source);
}
}
let mut ranges: Vec<TextRange> = old.iter().map(|c| c.range()).collect();
ranges.sort_by_key(|r| (r.start(), r.end()));
let mut trivia: Vec<SyntaxElement> = Vec::new();
let mut pos = usize::from(node.range().start());
for range in ranges {
let start = usize::from(range.start());
if pos < start {
lex_gap(source, pos, start, &mut trivia);
}
pos = pos.max(usize::from(range.end()));
}
let end = usize::from(node.range().end());
if pos < end {
lex_gap(source, pos, end, &mut trivia);
}
let mut children = Vec::with_capacity(old.len() + trivia.len());
let mut pending = trivia.into_iter().peekable();
for child in old {
while pending
.peek()
.is_some_and(|t| t.range().start() < child.range().start())
{
children.push(pending.next().unwrap());
}
children.push(child);
}
children.extend(pending);
node.set_children(children);
}
fn lex_gap(source: &str, start: usize, end: usize, out: &mut Vec<SyntaxElement>) {
let bytes = source.as_bytes();
let mut pos = start;
while pos < end {
if (pos == 0 || bytes[pos - 1] == b'\n')
&& let Some(line_end) = blank_line_end(bytes, pos, end)
{
push(out, SyntaxKind::BLANK_LINE, pos, line_end);
pos = line_end;
continue;
}
match bytes[pos] {
b'\n' => {
push(out, SyntaxKind::NEWLINE, pos, pos + 1);
pos += 1;
}
b'\r' if pos + 1 < end && bytes[pos + 1] == b'\n' => {
push(out, SyntaxKind::NEWLINE, pos, pos + 2);
pos += 2;
}
b' ' | b'\t' | b'\r' => {
let run_start = pos;
while pos < end && matches!(bytes[pos], b' ' | b'\t' | b'\r') {
if bytes[pos] == b'\r' && pos + 1 < end && bytes[pos + 1] == b'\n' {
break;
}
pos += 1;
}
push(out, SyntaxKind::WHITESPACE, run_start, pos);
}
_ => {
while pos < end && !matches!(bytes[pos], b' ' | b'\t' | b'\r' | b'\n') {
pos += 1;
}
}
}
}
}
fn blank_line_end(bytes: &[u8], pos: usize, end: usize) -> Option<usize> {
let mut i = pos;
while i < bytes.len() && matches!(bytes[i], b' ' | b'\t') {
i += 1;
}
let term = match bytes.get(i) {
Some(b'\n') => i + 1,
Some(b'\r') if bytes.get(i + 1) == Some(&b'\n') => i + 2,
Some(_) => return None,
None if i > pos => i,
None => return None,
};
(term <= end).then_some(term)
}
fn push(out: &mut Vec<SyntaxElement>, kind: SyntaxKind, start: usize, end: usize) {
out.push(SyntaxElement::Token(SyntaxToken::new(
kind,
TextRange::new(TextSize::new(start as u32), TextSize::new(end as u32)),
)));
}
#[cfg(test)]
mod tests {
use super::*;
fn lex_all(source: &str) -> Vec<(SyntaxKind, String)> {
let mut out = Vec::new();
lex_gap(source, 0, source.len(), &mut out);
out.iter()
.map(|e| match e {
SyntaxElement::Token(t) => (t.kind(), t.text(source).to_owned()),
SyntaxElement::Node(_) => unreachable!("lex_gap emits tokens only"),
})
.collect()
}
#[test]
fn test_lex_gap_newline_then_blank_line() {
let src = "x\n\ny";
let mut out = Vec::new();
lex_gap(src, 1, 3, &mut out);
let kinds: Vec<_> = out.iter().map(|e| e.kind()).collect();
assert_eq!(kinds, vec![SyntaxKind::NEWLINE, SyntaxKind::BLANK_LINE]);
}
#[test]
fn test_lex_gap_whitespace_only_line_is_blank_line_with_newline() {
assert_eq!(lex_all(" \t\n"), vec![(SyntaxKind::BLANK_LINE, " \t\n".to_owned())]);
}
#[test]
fn test_lex_gap_consecutive_blank_lines_one_token_each() {
assert_eq!(
lex_all("\n \n\n"),
vec![
(SyntaxKind::BLANK_LINE, "\n".to_owned()),
(SyntaxKind::BLANK_LINE, " \n".to_owned()),
(SyntaxKind::BLANK_LINE, "\n".to_owned()),
]
);
}
#[test]
fn test_lex_gap_trailing_whitespace_at_eof_is_blank_line_without_newline() {
assert_eq!(
lex_all("\n "),
vec![
(SyntaxKind::BLANK_LINE, "\n".to_owned()),
(SyntaxKind::BLANK_LINE, " ".to_owned()),
]
);
}
#[test]
fn test_lex_gap_indentation_before_content_is_whitespace() {
let src = " x";
let mut out = Vec::new();
lex_gap(src, 0, 4, &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].kind(), SyntaxKind::WHITESPACE);
}
#[test]
fn test_lex_gap_skips_dropped_content() {
assert_eq!(
lex_all("dropped words\n"),
vec![
(SyntaxKind::WHITESPACE, " ".to_owned()),
(SyntaxKind::NEWLINE, "\n".to_owned()),
]
);
}
#[test]
fn test_lex_gap_crlf_is_one_newline_token() {
assert_eq!(lex_all("x\r\n"), vec![(SyntaxKind::NEWLINE, "\r\n".to_owned())]);
}
}