Refine ahead_nontrivia search

Also reintroduces unsafe layers under another name
This commit is contained in:
Martin Haug 2022-06-01 17:58:04 +02:00
parent a937462491
commit 91bf1b7f65
2 changed files with 97 additions and 27 deletions

View File

@ -21,7 +21,7 @@ pub fn reparse(
) -> Range<usize> { ) -> Range<usize> {
if let SyntaxNode::Inner(inner) = root { if let SyntaxNode::Inner(inner) = root {
let reparser = Reparser { src, replaced, replacement_len }; let reparser = Reparser { src, replaced, replacement_len };
if let Some(range) = reparser.reparse_step(Arc::make_mut(inner), 0, true) { if let Some(range) = reparser.reparse_step(Arc::make_mut(inner), 0, true, true) {
return range; return range;
} }
} }
@ -52,13 +52,14 @@ impl Reparser<'_> {
node: &mut InnerNode, node: &mut InnerNode,
mut offset: usize, mut offset: usize,
outermost: bool, outermost: bool,
safe_to_replace: bool,
) -> Option<Range<usize>> { ) -> Option<Range<usize>> {
let is_markup = matches!(node.kind(), NodeKind::Markup(_)); let is_markup = matches!(node.kind(), NodeKind::Markup(_));
let original_count = node.children().len(); let original_count = node.children().len();
let original_offset = offset; let original_offset = offset;
let mut search = SearchState::default(); let mut search = SearchState::default();
let mut ahead_nontrivia = None; let mut ahead_nontrivia = AheadKind::None;
// Whether the first node that should be replaced is at start. // Whether the first node that should be replaced is at start.
let mut at_start = true; let mut at_start = true;
@ -93,6 +94,12 @@ impl Reparser<'_> {
{ {
search = SearchState::SpanFound(pos, pos); search = SearchState::SpanFound(pos, pos);
} else { } else {
// Update compulsary state of `ahead_nontrivia`.
match child.kind() {
NodeKind::Space(n) if n > &1 => ahead_nontrivia.newline(),
_ => {}
}
// We look only for non spaces, non-semicolon and also // We look only for non spaces, non-semicolon and also
// reject text that points to the special case for URL // reject text that points to the special case for URL
// evasion and line comments. // evasion and line comments.
@ -101,9 +108,15 @@ impl Reparser<'_> {
&& child.kind() != &NodeKind::Text('/'.into()) && child.kind() != &NodeKind::Text('/'.into())
&& (ahead_nontrivia.is_none() && (ahead_nontrivia.is_none()
|| self.replaced.start > child_span.end) || self.replaced.start > child_span.end)
&& !ahead_nontrivia.is_compulsory()
{ {
ahead_nontrivia = Some((pos, at_start)); ahead_nontrivia = if child.kind().is_unbounded() {
AheadKind::Unbounded(pos, at_start, true)
} else {
AheadKind::Normal(pos, at_start)
};
} }
at_start = child.kind().is_at_start(at_start); at_start = child.kind().is_at_start(at_start);
} }
} }
@ -139,14 +152,27 @@ impl Reparser<'_> {
} }
if let SearchState::Contained(pos) = search { if let SearchState::Contained(pos) = search {
// Do not allow replacement of elements inside of constructs whose
// opening and closing brackets look the same.
let safe_inside = match node.kind() {
NodeKind::Emph
| NodeKind::Strong
| NodeKind::Raw(_)
| NodeKind::Math(_) => false,
_ => true,
};
let child = &mut node.children_mut()[pos.idx]; let child = &mut node.children_mut()[pos.idx];
let prev_len = child.len(); let prev_len = child.len();
let prev_descendants = child.descendants(); let prev_descendants = child.descendants();
if let Some(range) = match child { if let Some(range) = match child {
SyntaxNode::Inner(node) => { SyntaxNode::Inner(node) => self.reparse_step(
self.reparse_step(Arc::make_mut(node), pos.offset, child_outermost) Arc::make_mut(node),
} pos.offset,
child_outermost,
safe_inside,
),
SyntaxNode::Leaf(_) => None, SyntaxNode::Leaf(_) => None,
} { } {
let new_len = child.len(); let new_len = child.len();
@ -177,19 +203,20 @@ impl Reparser<'_> {
} }
} }
// Save the current indent if this is a markup node and stop otherwise. // Make sure this is a markup node and that we may replace. If so, save
// the current indent.
let indent = match node.kind() { let indent = match node.kind() {
NodeKind::Markup(n) => *n, NodeKind::Markup(n) if safe_to_replace => *n,
_ => return None, _ => return None,
}; };
let (mut start, end) = search.done()?; let (mut start, end) = search.done()?;
if let Some((ahead, ahead_at_start)) = ahead_nontrivia { if let Some((ahead, ahead_at_start)) = ahead_nontrivia.data() {
let ahead_kind = node.children().as_slice()[ahead.idx].kind(); let ahead_kind = node.children().as_slice()[ahead.idx].kind();
if start.offset == self.replaced.start if start.offset == self.replaced.start
|| ahead_kind.only_at_start() || ahead_kind.only_at_start()
|| !ahead_kind.only_in_markup() || ahead_nontrivia.is_compulsory()
{ {
start = ahead; start = ahead;
at_start = ahead_at_start; at_start = ahead_at_start;
@ -315,6 +342,49 @@ impl SearchState {
} }
} }
/// What kind of ahead element is found at the moment, with an index and whether it is `at_start`
#[derive(Clone, Copy, Debug, PartialEq)]
enum AheadKind {
/// No non-trivia child has been found yet.
None,
/// A normal non-trivia child has been found.
Normal(NodePos, bool),
/// An unbounded child has been found. The last bool indicates whether it was on
/// the current line, in which case adding it to the reparsing range is
/// compulsory.
Unbounded(NodePos, bool, bool),
}
impl AheadKind {
fn newline(&mut self) {
match self {
Self::Unbounded(_, _, current_line) => {
*current_line = false;
}
_ => {}
}
}
fn data(self) -> Option<(NodePos, bool)> {
match self {
Self::None => None,
Self::Normal(pos, at_start) => Some((pos, at_start)),
Self::Unbounded(pos, at_start, _) => Some((pos, at_start)),
}
}
fn is_compulsory(self) -> bool {
match self {
Self::None | Self::Normal(_, _) => false,
Self::Unbounded(_, _, current_line) => current_line,
}
}
fn is_none(self) -> bool {
matches!(self, Self::None)
}
}
/// Which reparse function to choose for a span of elements. /// Which reparse function to choose for a span of elements.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum ReparseMode { enum ReparseMode {
@ -349,6 +419,8 @@ mod tests {
test("some content", 0..12, "", 0..0); test("some content", 0..12, "", 0..0);
test("", 0..0, "do it", 0..5); test("", 0..0, "do it", 0..5);
test("a d e", 1 .. 3, " b c d", 0 .. 9); test("a d e", 1 .. 3, " b c d", 0 .. 9);
test("*~ *", 2..2, "*", 0..5);
test("* {1+2} *", 5..6, "3", 2..7);
test("a #f() e", 1 .. 6, " b c d", 0 .. 9); test("a #f() e", 1 .. 6, " b c d", 0 .. 9);
test("a\nb\nc\nd\ne\n", 5 .. 5, "c", 2 .. 7); test("a\nb\nc\nd\ne\n", 5 .. 5, "c", 2 .. 7);
test("a\n\nb\n\nc\n\nd\n\ne\n", 7 .. 7, "c", 3 .. 10); test("a\n\nb\n\nc\n\nd\n\ne\n", 7 .. 7, "c", 3 .. 10);
@ -374,6 +446,7 @@ mod tests {
test(r#"a ```typst hello``` b"#, 16 .. 17, "", 2 .. 18); test(r#"a ```typst hello``` b"#, 16 .. 17, "", 2 .. 18);
test(r#"a ```typst hello```"#, 16 .. 17, "", 2 .. 18); test(r#"a ```typst hello```"#, 16 .. 17, "", 2 .. 18);
test("#for", 4 .. 4, "//", 0 .. 6); test("#for", 4 .. 4, "//", 0 .. 6);
test("#show a: f as b..", 16..16, "c", 0..18);
test("a\n#let \nb", 7 .. 7, "i", 2 .. 9); test("a\n#let \nb", 7 .. 7, "i", 2 .. 9);
test("a\n#for i \nb", 9 .. 9, "in", 2 .. 12); test("a\n#for i \nb", 9 .. 9, "in", 2 .. 12);
test("a~https://fun/html", 13..14, "n", 2..18); test("a~https://fun/html", 13..14, "n", 2..18);
@ -387,7 +460,7 @@ mod tests {
test("x = y", 1 .. 1, " + y\n", 0 .. 7); test("x = y", 1 .. 1, " + y\n", 0 .. 7);
test("abc\n= a heading\njoke", 3 .. 4, "\nmore\n\n", 0 .. 21); test("abc\n= a heading\njoke", 3 .. 4, "\nmore\n\n", 0 .. 21);
test("abc\n= a heading\njoke", 3 .. 4, "\nnot ", 0 .. 19); test("abc\n= a heading\njoke", 3 .. 4, "\nnot ", 0 .. 19);
test("#let x = (1, 2 + ;~ Five\r\n\r", 20 .. 23, "2.", 18 .. 23); test("#let x = (1, 2 + ;~ Five\r\n\r", 20 .. 23, "2.", 0 .. 23);
test("hey #myfriend", 4 .. 4, "\\", 0 .. 14); test("hey #myfriend", 4 .. 4, "\\", 0 .. 14);
test("hey #myfriend", 4 .. 4, "\\", 3 .. 6); test("hey #myfriend", 4 .. 4, "\\", 3 .. 6);
test("= foo\nbar\n - a\n - b", 6 .. 9, "", 0 .. 11); test("= foo\nbar\n - a\n - b", 6 .. 9, "", 0 .. 11);

View File

@ -841,27 +841,24 @@ impl NodeKind {
} }
} }
/// Whether this is a node that only appears in markup. /// Whether this is a node that is not clearly delimited by a character and
pub fn only_in_markup(&self) -> bool { /// may appear in markup.
pub fn is_unbounded(&self) -> bool {
matches!( matches!(
self, self,
Self::Text(_) Self::Strong
| Self::Linebreak { .. }
| Self::NonBreakingSpace
| Self::Shy
| Self::EnDash
| Self::EmDash
| Self::Ellipsis
| Self::Quote { .. }
| Self::Escape(_)
| Self::Strong
| Self::Emph | Self::Emph
| Self::Raw(_) | Self::Raw(_)
| Self::Math(_) | Self::Math(_)
| Self::Heading | Self::LetExpr
| Self::List | Self::SetExpr
| Self::Enum | Self::ShowExpr
| Self::EnumNumbering(_) | Self::WrapExpr
| Self::IfExpr
| Self::WhileExpr
| Self::ForExpr
| Self::IncludeExpr
| Self::ImportExpr
) )
} }