From 543d75da23425bb3f2c7208dfa4d56bc9031ce67 Mon Sep 17 00:00:00 2001
From: Pascal Kuthe <pascal.kuthe@semimod.de>
Date: Tue, 11 Oct 2022 08:48:12 +0200
Subject: [PATCH] do not reparse unmodified injections (#4146)

---
 Cargo.lock               |  1 +
 helix-core/Cargo.toml    |  1 +
 helix-core/src/syntax.rs | 73 ++++++++++++++++++++++++++++------------
 3 files changed, 54 insertions(+), 21 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d4befc96..38ab8044 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -388,6 +388,7 @@ name = "helix-core"
 version = "0.6.0"
 dependencies = [
  "arc-swap",
+ "bitflags",
  "chrono",
  "encoding_rs",
  "etcetera",
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index d7ab89ba..4eaadd1e 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -29,6 +29,7 @@ tree-sitter = "0.20"
 once_cell = "1.15"
 arc-swap = "1"
 regex = "1"
+bitflags = "1.3"
 
 log = "0.4"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index e0a984d2..8c51d5eb 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -8,13 +8,15 @@ use crate::{
 };
 
 use arc_swap::{ArcSwap, Guard};
+use bitflags::bitflags;
 use slotmap::{DefaultKey as LayerId, HopSlotMap};
 
 use std::{
     borrow::Cow,
     cell::RefCell,
-    collections::{HashMap, HashSet, VecDeque},
+    collections::{HashMap, VecDeque},
     fmt,
+    mem::replace,
     path::Path,
     str::FromStr,
     sync::Arc,
@@ -594,6 +596,7 @@ impl Syntax {
             tree: None,
             config,
             depth: 0,
+            flags: LayerUpdateFlags::empty(),
             ranges: vec![Range {
                 start_byte: 0,
                 end_byte: usize::MAX,
@@ -656,9 +659,10 @@ impl Syntax {
                 }
             }
 
-            for layer in &mut self.layers.values_mut() {
+            for layer in self.layers.values_mut() {
                 // The root layer always covers the whole range (0..usize::MAX)
                 if layer.depth == 0 {
+                    layer.flags = LayerUpdateFlags::MODIFIED;
                     continue;
                 }
 
@@ -689,6 +693,8 @@ impl Syntax {
                                 edit.new_end_position,
                                 point_sub(range.end_point, edit.old_end_position),
                             );
+
+                            layer.flags |= LayerUpdateFlags::MOVED;
                         }
                         // if the edit starts in the space before and extends into the range
                         else if edit.start_byte < range.start_byte {
@@ -703,11 +709,13 @@ impl Syntax {
                                 edit.new_end_position,
                                 point_sub(range.end_point, edit.old_end_position),
                             );
+                            layer.flags = LayerUpdateFlags::MODIFIED;
                         }
                         // If the edit is an insertion at the start of the tree, shift
                         else if edit.start_byte == range.start_byte && is_pure_insertion {
                             range.start_byte = edit.new_end_byte;
                             range.start_point = edit.new_end_position;
+                            layer.flags |= LayerUpdateFlags::MOVED;
                         } else {
                             range.end_byte = range
                                 .end_byte
@@ -717,6 +725,7 @@ impl Syntax {
                                 edit.new_end_position,
                                 point_sub(range.end_point, edit.old_end_position),
                             );
+                            layer.flags = LayerUpdateFlags::MODIFIED;
                         }
                     }
                 }
@@ -731,27 +740,33 @@ impl Syntax {
 
             let source_slice = source.slice(..);
 
-            let mut touched = HashSet::new();
-
-            // TODO: we should be able to avoid editing & parsing layers with ranges earlier in the document before the edit
-
             while let Some(layer_id) = queue.pop_front() {
-                // Mark the layer as touched
-                touched.insert(layer_id);
-
                 let layer = &mut self.layers[layer_id];
 
+                // Mark the layer as touched
+                layer.flags |= LayerUpdateFlags::TOUCHED;
+
                 // If a tree already exists, notify it of changes.
                 if let Some(tree) = &mut layer.tree {
-                    for edit in edits.iter().rev() {
-                        // Apply the edits in reverse.
-                        // If we applied them in order then edit 1 would disrupt the positioning of edit 2.
-                        tree.edit(edit);
+                    if layer
+                        .flags
+                        .intersects(LayerUpdateFlags::MODIFIED | LayerUpdateFlags::MOVED)
+                    {
+                        for edit in edits.iter().rev() {
+                            // Apply the edits in reverse.
+                            // If we applied them in order then edit 1 would disrupt the positioning of edit 2.
+                            tree.edit(edit);
+                        }
                     }
-                }
 
-                // Re-parse the tree.
-                layer.parse(&mut ts_parser.parser, source)?;
+                    if layer.flags.contains(LayerUpdateFlags::MODIFIED) {
+                        // Re-parse the tree.
+                        layer.parse(&mut ts_parser.parser, source)?;
+                    }
+                } else {
+                    // always parse if this layer has never been parsed before
+                    layer.parse(&mut ts_parser.parser, source)?;
+                }
 
                 // Switch to an immutable borrow.
                 let layer = &self.layers[layer_id];
@@ -855,6 +870,8 @@ impl Syntax {
                             config,
                             depth,
                             ranges,
+                            // set the modified flag to ensure the layer is parsed
+                            flags: LayerUpdateFlags::empty(),
                         })
                     });
 
@@ -868,8 +885,11 @@ impl Syntax {
             // Return the cursor back in the pool.
             ts_parser.cursors.push(cursor);
 
-            // Remove all untouched layers
-            self.layers.retain(|id, _| touched.contains(&id));
+            // Reset all `LayerUpdateFlags` and remove all untouched layers
+            self.layers.retain(|_, layer| {
+                replace(&mut layer.flags, LayerUpdateFlags::empty())
+                    .contains(LayerUpdateFlags::TOUCHED)
+            });
 
             Ok(())
         })
@@ -968,6 +988,16 @@ impl Syntax {
     // TODO: Folding
 }
 
+bitflags! {
+    /// Flags that track the status of a layer
+    /// in the `Sytaxn::update` function
+    struct LayerUpdateFlags : u32{
+        const MODIFIED = 0b001;
+        const MOVED = 0b010;
+        const TOUCHED = 0b100;
+    }
+}
+
 #[derive(Debug)]
 pub struct LanguageLayer {
     // mode
@@ -975,7 +1005,8 @@ pub struct LanguageLayer {
     pub config: Arc<HighlightConfiguration>,
     pub(crate) tree: Option<Tree>,
     pub ranges: Vec<Range>,
-    pub depth: usize,
+    pub depth: u32,
+    flags: LayerUpdateFlags,
 }
 
 impl LanguageLayer {
@@ -1191,7 +1222,7 @@ struct HighlightIter<'a> {
     layers: Vec<HighlightIterLayer<'a>>,
     iter_count: usize,
     next_event: Option<HighlightEvent>,
-    last_highlight_range: Option<(usize, usize, usize)>,
+    last_highlight_range: Option<(usize, usize, u32)>,
 }
 
 // Adapter to convert rope chunks to bytes
@@ -1224,7 +1255,7 @@ struct HighlightIterLayer<'a> {
     config: &'a HighlightConfiguration,
     highlight_end_stack: Vec<usize>,
     scope_stack: Vec<LocalScope<'a>>,
-    depth: usize,
+    depth: u32,
     ranges: &'a [Range],
 }