WIP: Push failing test for `DisplayMap::highlighted_chunks_at`

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs      |  42 +++++++++----
zed/src/editor/display_map/mod.rs | 101 ++++++++++++++++++++++++++++++++
zed/src/language.rs               |   6 
3 files changed, 131 insertions(+), 18 deletions(-)

Detailed changes

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

@@ -600,6 +600,10 @@ impl Buffer {
         }
     }
 
+    pub fn is_parsing(&self) -> bool {
+        self.is_parsing
+    }
+
     fn should_reparse(&self) -> bool {
         if let Some(syntax_tree) = self.syntax_tree.lock().as_ref() {
             !syntax_tree.parsed || syntax_tree.version != self.version
@@ -3481,13 +3485,15 @@ mod tests {
             let text = "fn a() {}";
 
             let buffer = Buffer::from_history(0, History::new(text.into()), None, rust_lang, ctx);
-            assert!(buffer.is_parsing);
+            assert!(buffer.is_parsing());
             assert!(buffer.syntax_tree().is_none());
             buffer
         });
 
         // Wait for the initial text to parse
-        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        buffer
+            .condition(&ctx, |buffer, _| !buffer.is_parsing())
+            .await;
         assert_eq!(
             get_tree_sexp(&buffer, &ctx),
             concat!(
@@ -3504,17 +3510,19 @@ mod tests {
 
             let offset = buf.text().find(")").unwrap();
             buf.edit(vec![offset..offset], "b: C", Some(ctx)).unwrap();
-            assert!(!buf.is_parsing);
+            assert!(!buf.is_parsing());
 
             let offset = buf.text().find("}").unwrap();
             buf.edit(vec![offset..offset], " d; ", Some(ctx)).unwrap();
-            assert!(!buf.is_parsing);
+            assert!(!buf.is_parsing());
 
             buf.end_transaction(None, Some(ctx)).unwrap();
             assert_eq!(buf.text(), "fn a(b: C) { d; }");
-            assert!(buf.is_parsing);
+            assert!(buf.is_parsing());
         });
-        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        buffer
+            .condition(&ctx, |buffer, _| !buffer.is_parsing())
+            .await;
         assert_eq!(
             get_tree_sexp(&buffer, &ctx),
             concat!(
@@ -3532,21 +3540,23 @@ mod tests {
             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);
+            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);
+            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);
+            assert!(buf.is_parsing());
         });
-        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        buffer
+            .condition(&ctx, |buffer, _| !buffer.is_parsing())
+            .await;
         assert_eq!(
             get_tree_sexp(&buffer, &ctx),
             concat!(
@@ -3563,9 +3573,11 @@ mod tests {
         buffer.update(&mut ctx, |buf, ctx| {
             buf.undo(Some(ctx));
             assert_eq!(buf.text(), "fn a() {}");
-            assert!(buf.is_parsing);
+            assert!(buf.is_parsing());
         });
-        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        buffer
+            .condition(&ctx, |buffer, _| !buffer.is_parsing())
+            .await;
         assert_eq!(
             get_tree_sexp(&buffer, &ctx),
             concat!(
@@ -3578,9 +3590,11 @@ mod tests {
         buffer.update(&mut ctx, |buf, ctx| {
             buf.redo(Some(ctx));
             assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
-            assert!(buf.is_parsing);
+            assert!(buf.is_parsing());
         });
-        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        buffer
+            .condition(&ctx, |buffer, _| !buffer.is_parsing())
+            .await;
         assert_eq!(
             get_tree_sexp(&buffer, &ctx),
             concat!(

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

@@ -445,7 +445,9 @@ pub fn collapse_tabs(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::test::*;
+    use crate::{language::Language, settings::Theme, test::*};
+    use buffer::History;
+    use std::sync::Arc;
 
     #[gpui::test]
     fn test_chunks_at(app: &mut gpui::MutableAppContext) {
@@ -486,6 +488,103 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_highlighted_chunks_at(mut app: gpui::TestAppContext) {
+        use unindent::Unindent as _;
+
+        let grammar = tree_sitter_rust::language();
+        let text = r#"
+            fn outer() {}
+
+            mod module {
+                fn inner() {}
+            }"#
+        .unindent();
+        let query = tree_sitter::Query::new(
+            grammar,
+            r#"
+            (mod_item name: (identifier) body: _ @mod.body)
+            (function_item name: (identifier) @fn.name)"#,
+        )
+        .unwrap();
+        let theme = Theme::parse(
+            r#"
+            [syntax]
+            "mod.body" = 0xff0000
+            "fn.name" = 0x00ff00"#,
+        )
+        .unwrap();
+        let lang = Arc::new(Language {
+            name: "Test".to_string(),
+            grammar: grammar.clone(),
+            highlight_query: query,
+            path_suffixes: vec![".test".to_string()],
+            theme_mapping: Default::default(),
+        });
+        lang.set_theme(&theme);
+
+        let buffer = app.add_model(|ctx| {
+            Buffer::from_history(0, History::new(text.into()), None, Some(lang), ctx)
+        });
+        buffer.condition(&app, |buf, _| !buf.is_parsing()).await;
+
+        let mut map = app.read(|ctx| DisplayMap::new(buffer, 2, ctx));
+        assert_eq!(
+            app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)),
+            vec![
+                ("fn ".to_string(), None),
+                ("outer".to_string(), Some("fn.name")),
+                ("() {}\n\nmod module ".to_string(), None),
+                ("{\n    fn ".to_string(), Some("mod.body")),
+                ("inner".to_string(), Some("fn.name")),
+                ("() {}\n}".to_string(), Some("mod.body")),
+            ]
+        );
+        assert_eq!(
+            app.read(|ctx| highlighted_chunks(3, &map, &theme, ctx)),
+            vec![
+                ("    fn ".to_string(), Some("mod.body")),
+                ("inner".to_string(), Some("fn.name")),
+                ("() {}\n}".to_string(), Some("mod.body")),
+            ]
+        );
+
+        app.read(|ctx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], ctx));
+        assert_eq!(
+            app.read(|ctx| highlighted_chunks(0, &map, &theme, ctx)),
+            vec![
+                ("fn ".to_string(), None),
+                ("out".to_string(), Some("fn.name")),
+                ("…".to_string(), None),
+                ("  fn ".to_string(), Some("mod.body")),
+                ("inner".to_string(), Some("fn.name")),
+                ("() {}\n}".to_string(), Some("mod.body")),
+            ]
+        );
+
+        fn highlighted_chunks<'a>(
+            row: u32,
+            map: &DisplayMap,
+            theme: &'a Theme,
+            ctx: &AppContext,
+        ) -> Vec<(String, Option<&'a str>)> {
+            let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
+            for (chunk, style_id) in map.snapshot(ctx).highlighted_chunks_at(row) {
+                let style_name = theme.syntax_style_name(style_id);
+                if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
+                    if style_name == *last_style_name {
+                        last_chunk.push_str(chunk);
+                    } else {
+                        chunks.push((chunk.to_string(), style_name));
+                    }
+                } else {
+                    chunks.push((chunk.to_string(), style_name));
+                }
+            }
+            chunks
+        }
+    }
+
     #[gpui::test]
     fn test_clip_point(app: &mut gpui::MutableAppContext) {
         use Bias::{Left, Right};

zed/src/language.rs 🔗

@@ -14,8 +14,8 @@ pub struct Language {
     pub name: String,
     pub grammar: Grammar,
     pub highlight_query: Query,
-    path_suffixes: Vec<String>,
-    theme_mapping: Mutex<ThemeMap>,
+    pub path_suffixes: Vec<String>,
+    pub theme_mapping: Mutex<ThemeMap>,
 }
 
 pub struct LanguageRegistry {
@@ -27,7 +27,7 @@ impl Language {
         self.theme_mapping.lock().clone()
     }
 
-    fn set_theme(&self, theme: &Theme) {
+    pub fn set_theme(&self, theme: &Theme) {
         *self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme);
     }
 }