use std::ops::Range;
use crate::{
Span, SyntaxKind, SyntaxNode, is_newline, parse, reparse_block, reparse_markup,
};
pub fn reparse(
root: &mut SyntaxNode,
text: &str,
replaced: Range<usize>,
replacement_len: usize,
) -> Range<usize> {
try_reparse(text, replaced, replacement_len, None, root, 0).unwrap_or_else(|| {
let id = root.span().id();
*root = parse(text);
if let Some(id) = id {
root.numberize(id, Span::FULL).unwrap();
}
0..text.len()
})
}
fn try_reparse(
text: &str,
replaced: Range<usize>,
replacement_len: usize,
parent_kind: Option<SyntaxKind>,
node: &mut SyntaxNode,
offset: usize,
) -> Option<Range<usize>> {
let (overlap, start_offset) = overlapping_children(node, replaced.clone(), offset)?;
let node_kind = node.kind();
let children = node.children_mut();
if let [child] = &mut children[overlap.clone()]
&& start_offset < replaced.start
&& replaced.end < start_offset + child.len()
&& child.is_inner()
{
let prev_len = child.len();
let prev_desc = child.descendants();
let new_len = prev_len + replacement_len - replaced.len();
let new_range = start_offset..start_offset + new_len;
if let Some(range) = try_reparse(
text,
replaced.clone(),
replacement_len,
Some(node_kind),
child,
start_offset,
) {
assert_eq!(child.len(), new_len);
let new_desc = child.descendants();
node.update_parent(prev_len, new_len, prev_desc, new_desc);
return Some(range);
}
if child.kind().is_block()
&& let Some(reparsed) = reparse_block(text, new_range.clone())
{
return node
.replace_children(overlap, vec![reparsed])
.is_ok()
.then_some(new_range);
}
}
if node_kind == SyntaxKind::Markup
&& matches!(parent_kind, None | Some(SyntaxKind::ContentBlock))
{
expand_and_reparse_markup(
text,
replaced,
replacement_len,
node,
overlap,
offset,
parent_kind.is_none(),
)
} else {
None
}
}
fn expand_and_reparse_markup(
text: &str,
replaced: Range<usize>,
replacement_len: usize,
node: &mut SyntaxNode,
overlap: Range<usize>,
offset: usize,
top_level: bool,
) -> Option<Range<usize>> {
let children = node.children().as_slice();
let mut expansion = 1;
loop {
let mut start = overlap.start.saturating_sub(expansion.max(2));
let mut end = (overlap.end + expansion).min(children.len());
while start > 0 && expand(&children[start]) {
start -= 1;
}
while end < children.len() && expand(&children[end]) {
end += 1;
}
if start > 0 && children[start - 1].kind() == SyntaxKind::Hash {
start -= 1;
}
let mut prefix_len = 0;
let mut nesting = 0;
let mut at_start = true;
for child in &children[..start] {
prefix_len += child.len();
next_at_start(child, &mut at_start);
next_nesting(child, &mut nesting);
}
let mut prev_len = 0;
let mut prev_at_start_after = at_start;
let mut prev_nesting_after = nesting;
for child in &children[start..end] {
prev_len += child.len();
next_at_start(child, &mut prev_at_start_after);
next_nesting(child, &mut prev_nesting_after);
}
let shifted = offset + prefix_len;
let new_len = prev_len + replacement_len - replaced.len();
let new_range = shifted..shifted + new_len;
let at_end = end == children.len();
let reparsed = reparse_markup(
text,
new_range.clone(),
&mut at_start,
&mut nesting,
top_level,
);
if let Some(newborns) = reparsed {
if (at_end || at_start == prev_at_start_after)
&& ((at_end && top_level) || nesting == prev_nesting_after)
{
return node
.replace_children(start..end, newborns)
.is_ok()
.then_some(new_range);
}
}
if start == 0 && at_end {
break;
}
expansion *= 2;
}
None
}
fn overlapping_children(
node: &SyntaxNode,
range: Range<usize>,
mut offset: usize,
) -> Option<(Range<usize>, usize)> {
if !node.is_inner() || offset > range.start || range.end > offset + node.len() {
return None;
}
let mut index = 0;
let mut start = 0;
let mut start_offset = offset;
for child in node.children() {
if offset < range.start {
start = index;
start_offset = offset;
}
offset += child.len();
index += 1;
if range.end < offset {
break;
}
}
debug_assert!(start_offset <= range.start);
debug_assert!(range.end <= offset);
Some((start..index, start_offset))
}
fn expand(node: &SyntaxNode) -> bool {
let kind = node.kind();
kind.is_trivia()
|| kind.is_error()
|| kind == SyntaxKind::Semicolon
|| node.leaf_text() == "/"
|| node.leaf_text() == ":"
}
fn next_at_start(node: &SyntaxNode, at_start: &mut bool) {
let kind = node.kind();
if kind.is_trivia() {
*at_start |= kind == SyntaxKind::Parbreak
|| (kind == SyntaxKind::Space && node.leaf_text().chars().any(is_newline));
} else {
*at_start = false;
}
}
fn next_nesting(node: &SyntaxNode, nesting: &mut usize) {
if node.kind() == SyntaxKind::Text {
match node.leaf_text().as_str() {
"[" => *nesting += 1,
"]" if *nesting > 0 => *nesting -= 1,
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use std::ops::Range;
use crate::{Source, Span, parse};
enum Edit {
End,
At(usize),
Range(Range<usize>),
Match(&'static str),
After(&'static str),
}
impl Edit {
#[track_caller]
fn into_range(self, text: &str) -> Range<usize> {
match self {
Self::End => text.len()..text.len(),
Self::At(index) => {
assert!(text.len() >= index, "index is out of bounds");
index..index
}
Self::Range(range) => {
assert!(text.len() >= range.end, "range is out of bounds");
range
}
Self::Match(pat) => {
let start = text.find(pat).expect("pattern must exist in original");
start..start + pat.len()
}
Self::After(pat) => {
let start = text.find(pat).expect("pattern must exist in original");
let end = start + pat.len();
end..end
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Reparse<'a> {
All,
Incr(&'a str),
}
#[track_caller]
fn test(text: &str, edit: Edit, with: &str, expected: Reparse) {
let mut source = Source::detached(text);
let orig_tree = source.root().clone();
let replaced_range = source.edit(edit.into_range(text), with);
let mut reparsed_tree = source.root().clone();
let mut normal_parse = parse(source.text());
reparsed_tree.synthesize(Span::detached());
normal_parse.synthesize(Span::detached());
if reparsed_tree != normal_parse {
eprintln!("Original source text: {text:?}");
eprintln!("Original tree:\n{orig_tree:#?}");
eprintln!("New source text: {:?}", source.text());
eprintln!("Reparsed tree:\n{reparsed_tree:#?}");
eprintln!("Normal parse tree:\n{normal_parse:#?}");
panic!("Reparsed tree did not match normal parse");
}
let actual = if replaced_range == (0..source.text().len()) {
Reparse::All
} else {
Reparse::Incr(&source.text()[replaced_range])
};
assert_eq!(actual, expected);
}
#[test]
fn test_reparse_basic() {
use Reparse::*;
test("some content", Edit::Match("some content"), "do it", All);
test("some content", Edit::Range(0..12), "", All);
test("", Edit::At(0), "do it", All);
test("", Edit::End, "do it", All);
test("some content", Edit::After("some content"), " do it", All);
}
#[test]
fn test_reparse_markup() {
use Reparse::*;
test("abc~def~gh~", Edit::Range(5..6), "+", Incr("abc~d+f~"));
test("~~~~~~~", Edit::Range(3..4), "A", Incr("~~~A~~"));
test("abc~~", Edit::Match("b"), "", Incr("ac~"));
test("~b~c~d~e", Edit::At(0), "a", Incr("a~b"));
test("a~b~c~d~", Edit::End, "e", Incr("~d~e"));
test("\nb\nc\nd\ne", Edit::At(0), "a", Incr("a\nb\n"));
test("a\nb\nc\nd\n", Edit::End, "e", Incr("c\nd\ne"));
test("#var. hello", Edit::Match(" "), " ", All);
test("#var;hello", Edit::Range(9..10), "a", All);
test("https:/world", Edit::After("/"), "/", All);
test("hello world", Edit::Match("world"), "walkers", All);
test("a d e", Edit::Match(" d"), " b c d", All);
test("~*~*~", Edit::At(2), "*", All);
test("::1\n2. a\n3", Edit::After(" "), "4", Incr("1\n2. 4a\n"));
test("* #{1+2} *", Edit::Match("2"), "3", Incr("{1+3}"));
test("#{(0, 1, 2)}", Edit::Match("1"), "11pt", Incr("{(0, 11pt, 2)}"));
test("\n= A heading", Edit::After("A"), "n evocative", All);
test("#call() abc~d", Edit::After("()"), "[]", Incr("#call()[] abc"));
test("a your thing a", Edit::Range(6..7), "a", All);
test("#grid(columns: (auto, 1fr, 40%))", Edit::Match("auto"), "4pt", All);
test(
"abc\n= a head\njoke",
Edit::Match("\n"),
"\nmore\n\n",
Incr("abc\nmore\n\n= a head\n"),
);
test("#show f: a => b..", Edit::End, "c", All);
test("#for", Edit::End, "//", All);
test("a\n#let \nb", Edit::At(7), "i", Incr("#let i\nb"));
test("#{{let x = z}; a = 1} b", Edit::At(7), "//", All);
test("a ```typst hello```", Edit::Range(16..17), "", All);
test("a{b}c", Edit::At(1), "#", All);
test("a#{b}c", Edit::Match("#"), "", All);
}
#[test]
fn test_reparse_block() {
use Reparse::*;
test("Hello #{ x + 1 }!", Edit::Match("x"), "abc", Incr("{ abc + 1 }"));
test("A#{}!", Edit::After("{"), "\"", All);
test("#{ [= x] }!", Edit::After("="), "=", Incr("== x"));
test("#[[]]", Edit::At(3), "\\", Incr("[[\\]]"));
test("#[[ab]]", Edit::Match("b"), "\\", Incr("[[a\\]]"));
test("#{}}", Edit::After("{"), "{", All);
test("A: #[BC]", Edit::After("B"), "{", Incr("B{C"));
test("A: #[BC]", Edit::After("B"), "#{", Incr("B#{C"));
test("A: #[BC]", Edit::After("B"), "#{}", Incr("B#{}C"));
test("#{\"ab\"}A", Edit::At(5), "c", Incr("{\"abc\"}"));
test("#{\"ab\"}A", Edit::Range(5..6), "c", All);
test("a#[]b", Edit::After("["), "#{", Incr("[#{]"));
test("a#{call(); abc}b", Edit::At(8), "[]", Incr("{call([]); abc}"));
test(
"a #while x {\n g(x) \n} b",
Edit::After("{"),
"//",
Incr("{//\n g(x) \n}"),
);
test("a#[]b", Edit::After("["), "[hey]", Incr("[[hey]]"));
}
#[test]
fn test_reparse_unclosed_embedded() {
use Reparse::*;
test("#\"a\nb\nc", Edit::End, "\"", All);
test("#return \"a\nb\nc", Edit::End, "\"", All);
test("#import \"@preview/pkg", Edit::End, "\"", All);
test("#import [a\nb\nc", Edit::End, "]", All);
test("#import $a\nb\nc", Edit::End, "$", All);
test("#set thing\"a\n", Edit::End, "\"", All);
test("#let x = \"a\nb\nc", Edit::End, "\"", All);
test("#let x = {\"a\nb\nc}", Edit::After("c"), "\"", Incr("{\"a\nb\nc\"}"));
test("#if x {\"a\nb\nc", Edit::End, "\"", All);
test("#if x \"a\nb\nc", Edit::End, "\"", All);
test("#if x \"a\nb\nc", Edit::After("x "), "{}", All);
test("#if x $a\nb\nc", Edit::End, "$", All);
test("#for x in \"a\nb\nc", Edit::End, "\"", All);
test("#for x \"a\nb\nc", Edit::After("x "), "in ", All);
}
}