Improve performance when detecting JSX auto close (#48622)

Marco Mihai Condrache and Smit Barmase created

Helps #48601

<img width="1649" height="1071" alt="image"
src="https://github.com/user-attachments/assets/ff3dfee0-cc65-430f-a5fa-b4b4c36e8183"
/>


`syntax_layers` does some offset conversion that might require getting
some chunks from the rope, which is quite expensive. For detecting
autoclose, we only use the language from those syntax layers, so having
a short path that skips all the conversion should skip some sum_tree
traversals.
   
I'm pretty sure other places would benefit from this as well, but I
haven't searched them yet.

Release Notes:

- N/A

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Change summary

crates/editor/src/jsx_tag_auto_close.rs            |  3 
crates/language/src/buffer.rs                      |  4 
crates/language/src/syntax_map.rs                  | 24 ++++
crates/language/src/syntax_map/syntax_map_tests.rs | 81 ++++++++++++++++
4 files changed, 110 insertions(+), 2 deletions(-)

Detailed changes

crates/editor/src/jsx_tag_auto_close.rs 🔗

@@ -318,8 +318,7 @@ pub(crate) fn refresh_enabled_in_any_buffer(
 
             let buffer = buffer.read(cx);
             let snapshot = buffer.snapshot();
-            for syntax_layer in snapshot.syntax_layers() {
-                let language = syntax_layer.language;
+            for language in snapshot.syntax_layers_languages() {
                 if language.config().jsx_tag_auto_close.is_none() {
                     continue;
                 }

crates/language/src/buffer.rs 🔗

@@ -3796,6 +3796,10 @@ impl BufferSnapshot {
             .layers_for_range(range, &self.text, include_hidden)
     }
 
+    pub fn syntax_layers_languages(&self) -> impl Iterator<Item = &Arc<Language>> {
+        self.syntax.languages(&self, true)
+    }
+
     pub fn smallest_syntax_layer_containing<D: ToOffset>(
         &self,
         range: Range<D>,

crates/language/src/syntax_map.rs 🔗

@@ -971,6 +971,30 @@ impl SyntaxSnapshot {
         )
     }
 
+    pub fn languages<'a>(
+        &'a self,
+        buffer: &'a BufferSnapshot,
+        include_hidden: bool,
+    ) -> impl Iterator<Item = &'a Arc<Language>> {
+        let mut cursor = self.layers.cursor::<()>(buffer);
+        cursor.next();
+        iter::from_fn(move || {
+            while let Some(layer) = cursor.item() {
+                let mut info = None;
+                if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
+                    if include_hidden || !language.config.hidden {
+                        info = Some(language);
+                    }
+                }
+                cursor.next();
+                if info.is_some() {
+                    return info;
+                }
+            }
+            None
+        })
+    }
+
     #[cfg(test)]
     pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer<'a>> {
         self.layers_for_range(0..buffer.len(), buffer, true)

crates/language/src/syntax_map/syntax_map_tests.rs 🔗

@@ -180,6 +180,87 @@ fn test_syntax_map_layers_for_range(cx: &mut App) {
     );
 }
 
+#[gpui::test]
+fn test_syntax_map_languages_match_layers_for_range(cx: &mut App) {
+    let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
+    let markdown = markdown_lang();
+    let markdown_inline = Arc::new(markdown_inline_lang());
+    registry.add(markdown.clone());
+    registry.add(markdown_inline);
+    registry.add(rust_lang());
+
+    let buffer = Buffer::new(
+        ReplicaId::LOCAL,
+        BufferId::new(1).unwrap(),
+        r#"
+            This is `inline`.
+
+            ```rs
+            fn a() {}
+            ```
+        "#
+        .unindent(),
+    );
+
+    let mut syntax_map = SyntaxMap::new(&buffer);
+    syntax_map.set_language_registry(registry);
+    syntax_map.reparse(markdown, &buffer);
+
+    let all_language_names = syntax_map
+        .languages(&buffer, true)
+        .map(|language| language.name().to_string())
+        .collect::<Vec<_>>();
+    let all_layer_language_names = syntax_map
+        .layers_for_range(0..buffer.len(), &buffer, true)
+        .map(|layer| layer.language.name().to_string())
+        .collect::<Vec<_>>();
+
+    assert_eq!(all_language_names, all_layer_language_names);
+    assert!(
+        all_language_names
+            .iter()
+            .any(|language_name| language_name == "Markdown-Inline"),
+        "expected hidden languages to be included when include_hidden is true"
+    );
+    assert!(
+        all_language_names
+            .iter()
+            .any(|language_name| language_name == "Markdown")
+    );
+    assert!(
+        all_language_names
+            .iter()
+            .any(|language_name| language_name == "Rust")
+    );
+
+    let visible_language_names = syntax_map
+        .languages(&buffer, false)
+        .map(|language| language.name().to_string())
+        .collect::<Vec<_>>();
+    let visible_layer_language_names = syntax_map
+        .layers_for_range(0..buffer.len(), &buffer, false)
+        .map(|layer| layer.language.name().to_string())
+        .collect::<Vec<_>>();
+
+    assert_eq!(visible_language_names, visible_layer_language_names);
+    assert!(
+        !visible_language_names
+            .iter()
+            .any(|language_name| language_name == "Markdown-Inline"),
+        "expected hidden languages to be excluded when include_hidden is false"
+    );
+    assert!(
+        visible_language_names
+            .iter()
+            .any(|language_name| language_name == "Markdown")
+    );
+    assert!(
+        visible_language_names
+            .iter()
+            .any(|language_name| language_name == "Rust")
+    );
+}
+
 #[gpui::test]
 fn test_dynamic_language_injection(cx: &mut App) {
     let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));