Add a unit test for reparsing a buffer

Max Brunsfeld created

Change summary

zed/src/editor/buffer/mod.rs | 103 +++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+), 2 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -2482,11 +2482,11 @@ impl ToPoint for usize {
 mod tests {
     use super::*;
     use crate::{
-        test::temp_tree,
+        test::{build_app_state, temp_tree},
         util::RandomCharIter,
         worktree::{Worktree, WorktreeHandle},
     };
-    use gpui::App;
+    use gpui::{App, ModelHandle};
     use rand::prelude::*;
     use serde_json::json;
     use std::{
@@ -3337,6 +3337,105 @@ mod tests {
         }
     }
 
+    #[gpui::test]
+    async fn test_reparse(mut ctx: gpui::TestAppContext) {
+        let app_state = ctx.read(build_app_state);
+        let rust_lang = app_state
+            .language_registry
+            .select_language("test.rs")
+            .cloned();
+        assert!(rust_lang.is_some());
+
+        let buffer = ctx.add_model(|ctx| {
+            let text = "fn a() {}";
+
+            let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx);
+            assert!(buffer.is_parsing);
+            assert!(buffer.tree.is_none());
+            buffer
+        });
+
+        // Wait for the initial text to parse
+        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        assert_eq!(
+            get_tree_sexp(&buffer, &ctx),
+            concat!(
+                "(source_file (function_item name: (identifier) ",
+                "parameters: (parameters) ",
+                "body: (block)))"
+            )
+        );
+
+        // Perform some edits (add parameter and variable reference)
+        // Parsing doesn't begin until the transaction is complete
+        buffer.update(&mut ctx, |buf, ctx| {
+            buf.start_transaction(None).unwrap();
+
+            let offset = buf.text().find(")").unwrap();
+            buf.edit(vec![offset..offset], "b: C", Some(ctx)).unwrap();
+            assert!(!buf.is_parsing);
+
+            let offset = buf.text().find("}").unwrap();
+            buf.edit(vec![offset..offset], " d; ", Some(ctx)).unwrap();
+            assert!(!buf.is_parsing);
+
+            buf.end_transaction(None, Some(ctx)).unwrap();
+            assert_eq!(buf.text(), "fn a(b: C) { d; }");
+            assert!(buf.is_parsing);
+        });
+        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        assert_eq!(
+            get_tree_sexp(&buffer, &ctx),
+            concat!(
+                "(source_file (function_item name: (identifier) ",
+                    "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
+                    "body: (block (identifier))))"
+            )
+        );
+
+        // Perform a series of edits without waiting for the current parse to complete:
+        // * turn identifier into a field expression
+        // * turn field expression into a method call
+        // * add a turbofish to the method call
+        buffer.update(&mut ctx, |buf, ctx| {
+            let offset = buf.text().find(";").unwrap();
+            buf.edit(vec![offset..offset], ".e", Some(ctx)).unwrap();
+            assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
+            assert!(buf.is_parsing);
+        });
+        buffer.update(&mut ctx, |buf, ctx| {
+            let offset = buf.text().find(";").unwrap();
+            buf.edit(vec![offset..offset], "(f)", Some(ctx)).unwrap();
+            assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
+            assert!(buf.is_parsing);
+        });
+        buffer.update(&mut ctx, |buf, ctx| {
+            let offset = buf.text().find("(f)").unwrap();
+            buf.edit(vec![offset..offset], "::<G>", Some(ctx)).unwrap();
+            assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
+            assert!(buf.is_parsing);
+        });
+        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        assert_eq!(
+            get_tree_sexp(&buffer, &ctx),
+            concat!(
+                "(source_file (function_item name: (identifier) ",
+                    "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
+                    "body: (block (call_expression ",
+                        "function: (generic_function ",
+                            "function: (field_expression value: (identifier) field: (field_identifier)) ",
+                            "type_arguments: (type_arguments (type_identifier))) ",
+                            "arguments: (arguments (identifier))))))",
+            )
+        );
+
+        fn get_tree_sexp(buffer: &ModelHandle<Buffer>, ctx: &gpui::TestAppContext) -> String {
+            buffer.read_with(ctx, |buffer, _| {
+                buffer.tree.as_ref().unwrap().root_node().to_sexp()
+            })
+        }
+    }
+
     impl Buffer {
         fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl Rng) -> Range<usize> {
             let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);