From d705c7f1ae9869c41906538e7e2c06c82641e82c Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Mon, 14 Sep 2026 20:29:36 +0000 Subject: [PATCH] Fix unhighlighted deleted regions in diffs (#64202) #63145 exposed a bug in the the `snapshot_with_edits` and `fast_forward` methods that we use to evolve the base text buffer for diffs: we preserved the value of the `tree_sitter_data` field from the old snapshot. That causes any diff whose base text has changed to have stale bracket ranges and now stale syntax highlighting as well, which can manifest as unhighlighted text in the deleted portion of an expanded diff hunk. This PR fixes the issue by clearing the `tree_sitter_data` field in `snapshot_with_edits`. Release Notes: - N/A (problem exists in nightly only) --- crates/language/src/buffer.rs | 1 + crates/language/src/buffer_tests.rs | 45 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9dedf63bc2..17ad2f3030 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3551,6 +3551,7 @@ impl Buffer { snapshot.text = new_text.snapshot.clone(); snapshot.syntax = syntax; + snapshot.tree_sitter_data = Arc::new(TreeSitterData::new(&snapshot.text)); EditedBufferSnapshot { text: new_text, diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index c7b835b474..72b96d970c 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -5460,6 +5460,51 @@ fn test_chunk_highlights_follow_edits_and_theme_changes(cx: &mut TestAppContext) ); } +#[gpui::test] +async fn test_snapshot_with_edits_refreshes_chunk_highlights(cx: &mut TestAppContext) { + cx.update(|cx| init_settings(cx, |_| {})); + + let language = keyword_and_function_lang(); + let theme = keyword_and_function_theme(); + language.set_theme(&theme); + + let keyword = theme_highlight_id(&theme, "keyword"); + let function = theme_highlight_id(&theme, "function"); + let row = "fn replacement() {}\n"; + let text = row.repeat(MAX_ROWS_IN_A_CHUNK as usize + 1); + + for original_text in ["", "fn original() {}"] { + let buffer = + cx.new(|cx| Buffer::local(original_text, cx).with_language(language.clone(), cx)); + cx.run_until_parked(); + let original_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + let original_highlights = + merged_highlight_runs(&original_snapshot, 0..original_snapshot.len()); + + let edited = buffer + .update(cx, |buffer, cx| { + buffer.snapshot_with_edits([(0..buffer.len(), text.clone())], cx) + }) + .await; + let snapshot = edited.snapshot(); + let expected = vec![ + ("fn".to_string(), keyword), + ("replacement".to_string(), function), + ]; + + assert_eq!(merged_highlight_runs(snapshot, 0..row.len()), expected); + let last_row_start = row.len() * MAX_ROWS_IN_A_CHUNK as usize; + assert_eq!( + merged_highlight_runs(snapshot, last_row_start..snapshot.len()), + expected, + ); + assert_eq!( + merged_highlight_runs(&original_snapshot, 0..original_snapshot.len()), + original_highlights, + ); + } +} + #[gpui::test] fn test_chunk_highlights_across_row_chunk_seeks(cx: &mut TestAppContext) { cx.update(|cx| init_settings(cx, |_| {}));