From 3917caa9a05510283cf78da3a7787842db87d108 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Wed, 5 Aug 2026 12:55:10 -0400 Subject: [PATCH] language: Defer auto-indent until parsing catches up (#62024) # Objective Prevent auto-indent from using stale syntax when text changes during a background parse. ## Solution Keep auto-indent pending and parsing active until the current parse completes. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Editor: Fixed auto-indent occasionally using stale syntax after rapid edits. --- crates/editor/src/editor_tests.rs | 5 ++++- crates/language/src/buffer.rs | 23 ++++++++++++++++++----- crates/languages/src/css.rs | 10 +++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 3e7abd3203..f4b29f75dd 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -35072,7 +35072,10 @@ async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) { let mut cx = EditorTestContext::new(cx).await; let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into()); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + cx.update_buffer(|buffer, cx| { + buffer.set_language(Some(language), cx); + buffer.set_sync_parse_timeout(None); + }); // test indents on comment insert cx.set_state(indoc! {" diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9f8e12aac6..e538fa0a03 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1874,7 +1874,12 @@ impl Buffer { language.clone(), sync_parse_timeout, ) { - self.did_finish_parsing(syntax_snapshot, Some(Duration::from_millis(300)), cx); + self.did_finish_parsing( + syntax_snapshot, + Some(Duration::from_millis(300)), + false, + cx, + ); self.reparse = None; return; } @@ -1906,7 +1911,7 @@ impl Buffer { let parse_again = this.version.changed_since(&parsed_version) || language_registry_changed() || grammar_changed(); - this.did_finish_parsing(new_syntax_map, None, cx); + this.did_finish_parsing(new_syntax_map, None, parse_again, cx); this.reparse = None; if parse_again { this.reparse(cx, false); @@ -1920,13 +1925,21 @@ impl Buffer { &mut self, syntax_snapshot: SyntaxSnapshot, block_budget: Option, + parse_again: bool, cx: &mut Context, ) { self.non_text_state_update_count += 1; self.syntax_map.lock().did_parse(syntax_snapshot); self.was_changed(); - self.request_autoindent(cx, block_budget); - self.parse_status.0.send(ParseStatus::Idle).unwrap(); + + let parsing_complete = !parse_again || self.language.is_none(); + if self.language.is_none() { + self.syntax_map.lock().clear(&self.text); + } + if parsing_complete { + self.request_autoindent(cx, block_budget); + self.parse_status.0.send(ParseStatus::Idle).unwrap(); + } Self::invalidate_tree_sitter_data(&mut self.tree_sitter_data, &self.text.snapshot()); cx.emit(BufferEvent::Reparsed); cx.notify(); @@ -3473,7 +3486,7 @@ impl Buffer { self.text.fast_forward(edited.text); if edited.snapshot.language == self.language { self.reparse = None; - self.did_finish_parsing(edited.snapshot.syntax, None, cx); + self.did_finish_parsing(edited.snapshot.syntax, None, false, cx); if did_edit { cx.emit(BufferEvent::Edited { source: BufferEditSource::User, diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index 5f796ed78e..cd5862d19c 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -232,7 +232,15 @@ mod tests { "# .unindent(); - let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx)); + let buffer = cx.new(|cx| { + let mut buffer = language::Buffer::local(text, cx); + buffer.set_sync_parse_timeout(None); + buffer.set_language(Some(language), cx); + buffer + }); + buffer + .read_with(cx, |buffer, _| buffer.parsing_idle()) + .await; let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None)); assert_eq!( outline