History

acp: Fix panic with edit file tool (#36732)

We had a frequent panic when the agent was using our edit file tool. The root cause was that we were constructing a `BufferDiff` with `BufferDiff::new`, then calling `set_base_text`, but not waiting for that asynchronous operation to finish. This means there was a window of time where the diff's base text was set to the initial value of `""`--that's not a problem in itself, but it was possible for us to call `PendingDiff::update` during that window, which calls `BufferDiff::update_diff`, which calls `BufferDiffSnapshot::new_with_base_buffer`, which takes two arguments `base_text` and `base_text_snapshot` that are supposed to represent the same text. We were getting the first of those arguments from the `base_text` field of `PendingDiff`, which is set immediately to the target base text without waiting for `BufferDiff::set_base_text` to run to completion; and the second from the `BufferDiff` itself, which still has the empty base text during that window. As a result of that mismatch, we could end up adding `DeletedHunk` diff transforms to the multibuffer for the diff card even though the multibuffer's base text was empty, ultimately leading to a panic very far away in rendering code. I've fixed this by adding a new `BufferDiff` constructor for the case where the buffer contents and the base text are (initially) the same, like for the diff cards, and so we don't need an async diff calculation. I also added a debug assertion to catch the basic issue here earlier, when `BufferDiffSnapshot::new_with_base_buffer` is called with two base texts that don't match. Release Notes: - N/A ---------

Cole Miller committed 1 year ago

Commit d88fd001 parent f4ba7992 files · +57 −16Browse files

Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 2 files). Show all

Unified · Split

crates/buffer_diff/src/buffer_diff.rs+32−1
@@ -162,6 +162,22 @@ impl BufferDiffSnapshot { · 161 unmodified lines
162162 }
163163 }
164164
165+ fn unchanged(
166+ buffer: &text::BufferSnapshot,
167+ base_text: language::BufferSnapshot,
168+ ) -> BufferDiffSnapshot {
169+ debug_assert_eq!(buffer.text(), base_text.text());
170+ BufferDiffSnapshot {
171+ inner: BufferDiffInner {
172+ base_text,
173+ hunks: SumTree::new(buffer),
174+ pending_hunks: SumTree::new(buffer),
175+ base_text_exists: false,
176+ },
177+ secondary_diff: None,
178+ }
179+ }
180+
165181 fn new_with_base_text(
166182 buffer: text::BufferSnapshot,
167183 base_text: Option<Arc<String>>,
@@ -213,7 +229,10 @@ impl BufferDiffSnapshot { · 45 unmodified lines
213229 cx: &App,
214230 ) -> impl Future<Output = Self> + use<> {
215231 let base_text_exists = base_text.is_some();
216− let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));
232+ let base_text_pair = base_text.map(|text| {
233+ debug_assert_eq!(&*text, &base_text_snapshot.text());
234+ (text, base_text_snapshot.as_rope().clone())
235+ });
217236 cx.background_executor()
218237 .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
219238 Self {
@@ -873,6 +892,18 @@ impl BufferDiff { · 653 unmodified lines
873892 }
874893 }
875894
895+ pub fn new_unchanged(
896+ buffer: &text::BufferSnapshot,
897+ base_text: language::BufferSnapshot,
898+ ) -> Self {
899+ debug_assert_eq!(buffer.text(), base_text.text());
900+ BufferDiff {
901+ buffer_id: buffer.remote_id(),
902+ inner: BufferDiffSnapshot::unchanged(buffer, base_text).inner,
903+ secondary_diff: None,
904+ }
905+ }
906+
876907 #[cfg(any(test, feature = "test-support"))]
877908 pub fn new_with_base_text(
878909 base_text: &str,