Don't reuse old syntax tree when resetting a buffer's language

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                    |  1 
crates/language/Cargo.toml    |  1 
crates/language/src/buffer.rs |  1 
crates/language/src/tests.rs  | 47 +++++++++++++++++++++++++++++++++---
4 files changed, 45 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2727,6 +2727,7 @@ dependencies = [
  "text",
  "theme",
  "tree-sitter",
+ "tree-sitter-json",
  "tree-sitter-rust",
  "unindent",
  "util",

crates/language/Cargo.toml 🔗

@@ -57,5 +57,6 @@ util = { path = "../util", features = ["test-support"] }
 ctor = "0.1"
 env_logger = "0.8"
 rand = "0.8.3"
+tree-sitter-json = "0.19.0"
 tree-sitter-rust = "0.20.0"
 unindent = "0.1.7"

crates/language/src/buffer.rs 🔗

@@ -486,6 +486,7 @@ impl Buffer {
     }
 
     pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
+        *self.syntax_tree.lock() = None;
         self.language = language;
         self.reparse(cx);
     }

crates/language/src/tests.rs 🔗

@@ -276,12 +276,32 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
             "arguments: (arguments (identifier)))))))",
         )
     );
+}
 
-    fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
-        buffer.read_with(cx, |buffer, _| {
-            buffer.syntax_tree().unwrap().root_node().to_sexp()
-        })
-    }
+#[gpui::test]
+async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
+    let buffer = cx.add_model(|cx| {
+        let mut buffer = Buffer::new(0, "{}", cx).with_language(Arc::new(rust_lang()), cx);
+        buffer.set_sync_parse_timeout(Duration::ZERO);
+        buffer
+    });
+
+    // Wait for the initial text to parse
+    buffer
+        .condition(&cx, |buffer, _| !buffer.is_parsing())
+        .await;
+    assert_eq!(
+        get_tree_sexp(&buffer, &cx),
+        "(source_file (expression_statement (block)))"
+    );
+
+    buffer.update(cx, |buffer, cx| {
+        buffer.set_language(Some(Arc::new(json_lang())), cx)
+    });
+    buffer
+        .condition(&cx, |buffer, _| !buffer.is_parsing())
+        .await;
+    assert_eq!(get_tree_sexp(&buffer, &cx), "(document (object))");
 }
 
 #[gpui::test]
@@ -978,6 +998,23 @@ fn rust_lang() -> Language {
     .unwrap()
 }
 
+fn json_lang() -> Language {
+    Language::new(
+        LanguageConfig {
+            name: "Json".into(),
+            path_suffixes: vec!["js".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_json::language()),
+    )
+}
+
+fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
+    buffer.read_with(cx, |buffer, _| {
+        buffer.syntax_tree().unwrap().root_node().to_sexp()
+    })
+}
+
 fn empty(point: Point) -> Range<Point> {
     point..point
 }