WIP: Start on `SyntaxMapSnapshot::unknown_injection_languages`

Antonio Scandurra created

Change summary

crates/language/src/syntax_map.rs | 50 ++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+), 1 deletion(-)

Detailed changes

crates/language/src/syntax_map.rs 🔗

@@ -7,6 +7,7 @@ use std::{
     cell::RefCell,
     cmp::{self, Ordering, Reverse},
     collections::BinaryHeap,
+    iter,
     ops::{Deref, DerefMut, Range},
     sync::Arc,
 };
@@ -133,6 +134,7 @@ struct SyntaxLayerSummary {
     range: Range<Anchor>,
     last_layer_range: Range<Anchor>,
     last_layer_language: Option<usize>,
+    contains_pending_layer: bool,
 }
 
 #[derive(Clone, Debug)]
@@ -642,7 +644,7 @@ impl SyntaxSnapshot {
         });
 
         cursor.next(buffer);
-        std::iter::from_fn(move || {
+        iter::from_fn(move || {
             while let Some(layer) = cursor.item() {
                 if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
                     let info = SyntaxLayerInfo {
@@ -662,6 +664,27 @@ impl SyntaxSnapshot {
             None
         })
     }
+
+    pub fn unknown_injection_languages<'a>(
+        &'a self,
+        buffer: &'a BufferSnapshot,
+    ) -> impl 'a + Iterator<Item = &Arc<str>> {
+        let mut cursor = self
+            .layers
+            .filter::<_, ()>(|summary| summary.contains_pending_layer);
+        cursor.next(buffer);
+        iter::from_fn(move || {
+            while let Some(layer) = cursor.item() {
+                if let SyntaxLayerContent::Pending { language_name } = &layer.content {
+                    cursor.next(buffer);
+                    return Some(language_name);
+                } else {
+                    cursor.next(buffer);
+                }
+            }
+            None
+        })
+    }
 }
 
 impl<'a> SyntaxMapCaptures<'a> {
@@ -1356,6 +1379,7 @@ impl Default for SyntaxLayerSummary {
             range: Anchor::MAX..Anchor::MIN,
             last_layer_range: Anchor::MIN..Anchor::MAX,
             last_layer_language: None,
+            contains_pending_layer: false,
         }
     }
 }
@@ -1377,6 +1401,7 @@ impl sum_tree::Summary for SyntaxLayerSummary {
         }
         self.last_layer_range = other.last_layer_range.clone();
         self.last_layer_language = other.last_layer_language;
+        self.contains_pending_layer |= other.contains_pending_layer;
     }
 }
 
@@ -1427,6 +1452,7 @@ impl sum_tree::Item for SyntaxLayer {
             range: self.range.clone(),
             last_layer_range: self.range.clone(),
             last_layer_language: self.content.language_id(),
+            contains_pending_layer: matches!(self.content, SyntaxLayerContent::Pending { .. }),
         }
     }
 }
@@ -1715,6 +1741,28 @@ mod tests {
                 "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...",
             ],
         );
+
+        // Replace Ruby with a language that hasn't been loaded yet.
+        let macro_name_range = range_for_text(&buffer, "ruby");
+        buffer.edit([(macro_name_range, "erb")]);
+        syntax_map.interpolate(&buffer);
+        syntax_map.reparse(markdown.clone(), &buffer);
+        assert_layers_for_range(
+            &syntax_map,
+            &buffer,
+            Point::new(3, 0)..Point::new(3, 0),
+            &[
+                "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..."
+            ],
+        );
+        assert_eq!(
+            syntax_map
+                .unknown_injection_languages(&buffer)
+                .collect::<Vec<_>>(),
+            vec![&Arc::from("erb")]
+        );
+
+        registry.add(Arc::new(erb_lang()));
     }
 
     #[gpui::test]