mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Introduce incremental parsing
This commit is contained in:
parent
52761a3baa
commit
1e4cab393e
@ -25,6 +25,20 @@ pub fn parse(src: &str) -> Rc<GreenNode> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a block. Returns `Some` if there was only one block.
|
||||
pub fn parse_block(source: &str) -> Option<Rc<GreenNode>> {
|
||||
let mut p = Parser::new(source);
|
||||
block(&mut p);
|
||||
if p.eof() {
|
||||
match p.finish().into_iter().next() {
|
||||
Some(Green::Node(node)) => Some(node),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse markup.
|
||||
fn markup(p: &mut Parser) {
|
||||
markup_while(p, true, &mut |_| true)
|
||||
|
@ -268,7 +268,7 @@ impl SourceFile {
|
||||
/// This panics if the `replace` range is out of bounds.
|
||||
pub fn edit(&mut self, replace: Range<usize>, with: &str) {
|
||||
let start = replace.start;
|
||||
self.src.replace_range(replace, with);
|
||||
self.src.replace_range(replace.clone(), with);
|
||||
|
||||
// Remove invalidated line starts.
|
||||
let line = self.byte_to_line(start).unwrap();
|
||||
@ -283,10 +283,41 @@ impl SourceFile {
|
||||
self.line_starts
|
||||
.extend(newlines(&self.src[start ..]).map(|idx| start + idx));
|
||||
|
||||
// Reparse.
|
||||
// Update the root node.
|
||||
#[cfg(not(feature = "parse-cache"))]
|
||||
{
|
||||
self.root = parse(&self.src);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parse-cache")]
|
||||
{
|
||||
let insertion_span = replace.into_span(self.id);
|
||||
let incremental_target =
|
||||
Rc::make_mut(&mut self.root).incremental_parent(insertion_span);
|
||||
|
||||
match incremental_target {
|
||||
Some((child_idx, parent, offset)) => {
|
||||
let child = &parent.children()[child_idx];
|
||||
let src = &self.src[offset .. offset + child.len()];
|
||||
let parse_res = match child.kind() {
|
||||
NodeKind::Markup => Some(parse(src)),
|
||||
_ => parse_block(src),
|
||||
}
|
||||
.and_then(|x| x.data().erroneous().not().then(|| x));
|
||||
|
||||
if let Some(parse_res) = parse_res {
|
||||
parent.replace_child(child_idx, parse_res);
|
||||
} else {
|
||||
self.root = parse(&self.src);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.root = parse(&self.src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide highlighting categories for the given range of the source file.
|
||||
pub fn highlight<F>(&self, range: Range<usize>, mut f: F)
|
||||
where
|
||||
@ -473,4 +504,21 @@ mod tests {
|
||||
// Test removing everything.
|
||||
test(TEST, 0 .. 21, "", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_source_file_edit_2() {
|
||||
#[track_caller]
|
||||
fn test(prev: &str, range: Range<usize>, with: &str, after: &str) {
|
||||
let mut source = SourceFile::detached(prev);
|
||||
let result = SourceFile::detached(after);
|
||||
dbg!(Green::from(source.root.clone()));
|
||||
source.edit(range, with);
|
||||
assert_eq!(source.src, result.src);
|
||||
assert_eq!(source.line_starts, result.line_starts);
|
||||
dbg!(Green::from(source.root));
|
||||
}
|
||||
|
||||
// Test inserting at the begining.
|
||||
test("abc #f()[def] ghi", 10 .. 11, "xyz", "abc #f()[dxyzf] ghi");
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,92 @@ impl GreenNode {
|
||||
pub fn children(&self) -> &[Green] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
/// The node's children, mutably.
|
||||
pub fn children_mut(&mut self) -> &mut [Green] {
|
||||
&mut self.children
|
||||
}
|
||||
|
||||
/// The node's metadata.
|
||||
pub fn data(&self) -> &GreenData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// The node's type.
|
||||
pub fn kind(&self) -> &NodeKind {
|
||||
self.data().kind()
|
||||
}
|
||||
|
||||
/// The node's length.
|
||||
pub fn len(&self) -> usize {
|
||||
self.data().len()
|
||||
}
|
||||
|
||||
/// Find the parent of the deepest incremental-safe node and the index of
|
||||
/// the found child.
|
||||
pub fn incremental_parent(
|
||||
&mut self,
|
||||
span: Span,
|
||||
) -> Option<(usize, &mut GreenNode, usize)> {
|
||||
self.incremental_parent_internal(span, 0)
|
||||
}
|
||||
|
||||
fn incremental_parent_internal(
|
||||
&mut self,
|
||||
span: Span,
|
||||
mut offset: usize,
|
||||
) -> Option<(usize, &mut GreenNode, usize)> {
|
||||
let x = unsafe { &mut *(self as *mut _) };
|
||||
|
||||
for (i, child) in self.children.iter_mut().enumerate() {
|
||||
match child {
|
||||
Green::Token(n) => {
|
||||
if offset < span.start {
|
||||
// the token is strictly before the span
|
||||
offset += n.len();
|
||||
} else {
|
||||
// the token is within or after the span; tokens are
|
||||
// never safe, so we return.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Green::Node(n) => {
|
||||
let end = n.len() + offset;
|
||||
if offset < span.start && end < span.start {
|
||||
// the node is strictly before the span
|
||||
offset += n.len();
|
||||
} else if span.start >= offset
|
||||
&& span.start < end
|
||||
&& span.end <= end
|
||||
&& span.end > offset
|
||||
{
|
||||
// the node is within the span.
|
||||
if n.kind().is_incremental_safe() {
|
||||
let res =
|
||||
Rc::make_mut(n).incremental_parent_internal(span, offset);
|
||||
if res.is_none() {
|
||||
return Some((i, x, offset));
|
||||
}
|
||||
} else {
|
||||
return Rc::make_mut(n)
|
||||
.incremental_parent_internal(span, offset);
|
||||
}
|
||||
} else {
|
||||
// the node is overlapping or after after the span; nodes are
|
||||
// never safe, so we return.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
/// Replace one of the node's children.
|
||||
pub fn replace_child(&mut self, index: usize, child: impl Into<Green>) {
|
||||
self.children[index] = child.into();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GreenNode> for Green {
|
||||
@ -653,6 +739,14 @@ impl NodeKind {
|
||||
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
|
||||
}
|
||||
|
||||
/// Whether it is safe to do incremental parsing on this node.
|
||||
pub fn is_incremental_safe(&self) -> bool {
|
||||
match self {
|
||||
Self::Block | Self::Markup => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A human-readable name for the kind.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
|
@ -125,6 +125,17 @@ impl Span {
|
||||
*self = self.join(other)
|
||||
}
|
||||
|
||||
/// Create a new span with n characters inserted inside of this span.
|
||||
pub fn inserted(mut self, other: Self, n: usize) -> Self {
|
||||
if !self.contains(other.start) || !self.contains(other.end) {
|
||||
panic!();
|
||||
}
|
||||
|
||||
let len_change = (n as isize - other.len() as isize) as usize;
|
||||
self.end += len_change;
|
||||
self
|
||||
}
|
||||
|
||||
/// Test whether a position is within the span.
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
self.start <= pos && self.end >= pos
|
||||
|
Loading…
x
Reference in New Issue
Block a user