From de7f2f0186e48c90dc1b18b202d0ac290cb36594 Mon Sep 17 00:00:00 2001 From: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:16:28 +0100 Subject: [PATCH] Improve performance when detecting JSX auto close (#48622) Helps #48601 image `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 --- crates/editor/src/jsx_tag_auto_close.rs | 3 +- crates/language/src/buffer.rs | 4 + crates/language/src/syntax_map.rs | 24 ++++++ .../src/syntax_map/syntax_map_tests.rs | 81 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index 8d8f8d43b1ad1d374b130a9b4bc83297e4a76214..538173c45e51697f2a648c1ed3c97c3d6c828565 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/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; } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 998bc23e8ee4deb8d5ef547f7992d79e6bc6d13a..c609c51ef995972000010e837d6c731bbf9a2496 100644 --- a/crates/language/src/buffer.rs +++ b/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> { + self.syntax.languages(&self, true) + } + pub fn smallest_syntax_layer_containing( &self, range: Range, diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 2922e359d2b8f610a3c4ffb365270eb94cc4f50c..d1da17d9a380224b34d5f809f6bee1ccaba46a29 100644 --- a/crates/language/src/syntax_map.rs +++ b/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> { + 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> { self.layers_for_range(0..buffer.len(), buffer, true) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 2ee37f19df022a8c3fe1a27924c1542424690b22..b7fec897b98aed7902cd25de65e008ba58ee55f9 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/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::>(); + let all_layer_language_names = syntax_map + .layers_for_range(0..buffer.len(), &buffer, true) + .map(|layer| layer.language.name().to_string()) + .collect::>(); + + 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::>(); + let visible_layer_language_names = syntax_map + .layers_for_range(0..buffer.len(), &buffer, false) + .map(|layer| layer.language.name().to_string()) + .collect::>(); + + 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()));