diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d1f8428f3167d5ad9d77035a9336ea947ac1f2e4..38a389f1e306439ee459b029ff827d8cbdba7469 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -287,6 +287,12 @@ impl Companion { }; let Some(excerpt) = patches.into_iter().next() else { + if cfg!(any(test, debug_assertions)) { + assert!( + our_snapshot.max_point() == Point::zero(), + "`patches_for_*_in_range` is only allowed to return an empty vec if the multibuffer is empty" + ); + } return Point::zero()..our_snapshot.max_point(); }; excerpt.patch.edit_for_old_position(point).new diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 77a3bf63c01f558dbc4cd0064a8769df7e551c35..43479f1713c9051023f3ef6ff168242e663df174 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -207,14 +207,12 @@ where return; }; - let Some(diff) = - source_snapshot.diff_for_buffer_id(first.source_buffer_snapshot.remote_id()) - else { + let source_buffer_id = first.source_buffer_snapshot.remote_id(); + let Some(diff) = source_snapshot.diff_for_buffer_id(source_buffer_id) else { pending.clear(); return; }; - let source_is_lhs = - first.source_buffer_snapshot.remote_id() == diff.base_text().remote_id(); + let source_is_lhs = source_buffer_id == diff.base_text().remote_id(); let target_buffer_id = if source_is_lhs { diff.buffer_id() } else { @@ -232,28 +230,34 @@ where let patch = translate_fn(diff, union_start..=union_end, rhs_buffer); - for excerpt in pending.drain(..) { - let target_position = patch.old_to_new(excerpt.buffer_point_range.start); - let target_position = target_buffer.anchor_before(target_position); - let Some(target_position) = target_snapshot.anchor_in_excerpt(target_position) else { - continue; - }; - let Some((target_buffer_snapshot, target_excerpt_range)) = - target_snapshot.excerpt_containing(target_position..target_position) - else { - continue; - }; + let mut source_excerpts = source_snapshot + .excerpts_for_buffer(source_buffer_id) + .peekable(); + let mut target_excerpts = target_snapshot + .excerpts_for_buffer(target_buffer_id) + .peekable(); - result.push(patch_for_excerpt( - source_snapshot, - target_snapshot, - &excerpt.source_buffer_snapshot, - target_buffer_snapshot, - excerpt.source_excerpt_range, - target_excerpt_range, - &patch, - excerpt.buffer_point_range, - )); + for excerpt in pending.drain(..) { + while let Some(source_excerpt_range) = source_excerpts.peek() + && source_excerpt_range != &excerpt.source_excerpt_range + { + source_excerpts.next(); + target_excerpts.next(); + } + if let Some(source_excerpt_range) = source_excerpts.peek() + && let Some(target_excerpt_range) = target_excerpts.peek() + { + result.push(patch_for_excerpt( + source_snapshot, + target_snapshot, + &excerpt.source_buffer_snapshot, + target_buffer, + source_excerpt_range.clone(), + target_excerpt_range.clone(), + &patch, + excerpt.buffer_point_range, + )); + } } }; diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 944568d4e2fa865e60e67c0fbfde771ba6d4f36d..9f5b65d75601aea5bcf3f42fe643bd29b3485a17 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -6,7 +6,7 @@ use crate::{ use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; -use collections::{HashMap, HashSet}; +use collections::HashMap; use editor::{ Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, @@ -768,7 +768,7 @@ impl ProjectDiff { needs_fold } - #[instrument(skip_all)] + #[instrument(skip(this, cx))] pub async fn refresh( this: WeakEntity, reason: RefreshReason, @@ -780,13 +780,13 @@ impl ProjectDiff { let load_buffers = branch_diff.load_buffers(cx); (branch_diff.repo().cloned(), load_buffers) }); - let mut previous_paths = this + let mut previous_buffers = this .multibuffer .read(cx) .snapshot(cx) .buffers_with_paths() - .map(|(_, path_key)| path_key.clone()) - .collect::>(); + .map(|(buffer_snapshot, path_key)| (path_key.clone(), buffer_snapshot.remote_id())) + .collect::>(); if let Some(repo) = repo { let repo = repo.read(cx); @@ -796,14 +796,14 @@ impl ProjectDiff { let sort_prefix = sort_prefix(&repo, &entry.repo_path, entry.file_status, cx); let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.as_ref().clone()); - previous_paths.remove(&path_key); + previous_buffers.remove(&path_key); path_keys.push(path_key) } } this.editor.update(cx, |editor, cx| { - for path in previous_paths { - if let Some(buffer) = this.multibuffer.read(cx).buffer_for_path(&path, cx) { + for (path, buffer_id) in previous_buffers { + if let Some(buffer) = this.multibuffer.read(cx).buffer(buffer_id) { let skip = match reason { RefreshReason::DiffChanged | RefreshReason::EditorSaved => { buffer.read(cx).is_dirty() @@ -816,6 +816,8 @@ impl ProjectDiff { } this.buffer_diff_subscriptions.remove(&path.path); + let _span = ztracing::info_span!("remove_excerpts_for_path"); + _span.enter(); editor.remove_excerpts_for_path(path, cx); } }); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 801c7728a80a47a75254f1712b42a7e10a034ea8..59272d4b7c582f30ca4f1fb3f53344c1e7361bf8 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3636,6 +3636,7 @@ impl MultiBufferSnapshot { result } + /// Callers should not provide a range where `end < start` pub fn range_to_buffer_ranges( &self, range: Range, @@ -3647,6 +3648,7 @@ impl MultiBufferSnapshot { let mut cursor = self.cursor::(); let start = range.start.to_offset(self); let end = range.end.to_offset(self); + let range_non_empty = end > start; cursor.seek(&start); let mut result: Vec<( @@ -3655,7 +3657,7 @@ impl MultiBufferSnapshot { ExcerptRange, )> = Vec::new(); while let Some(region) = cursor.region() { - if region.range.start >= end { + if region.range.start > end || (region.range.start == end && range_non_empty) { break; } if region.is_main_buffer { diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index cebc9073e9d87a3c6eaf71d78e181d3e833ad56a..a7f4b18cc423956db105e670def8ccbebbf49941 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -5864,6 +5864,48 @@ fn test_range_to_buffer_ranges(cx: &mut App) { assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0)); } +#[gpui::test] +fn test_range_to_buffer_ranges_zero_length_at_excerpt_boundary(cx: &mut App) { + let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx)); + let buffer_2 = cx.new(|cx| Buffer::local("ccc\nddd", cx)); + + let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.set_excerpts_for_path( + PathKey::sorted(0), + buffer_1.clone(), + [Point::new(0, 0)..Point::new(1, 3)], + 0, + cx, + ); + multibuffer.set_excerpts_for_path( + PathKey::sorted(1), + buffer_2.clone(), + [Point::new(0, 0)..Point::new(1, 3)], + 0, + cx, + ); + }); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "aaa\nbbb\nccc\nddd"); + + // This point is right at the start of the very first excerpt, so if we get + // a buffer range, we should get `0..0` + let excerpt_2_start = Point::new(2, 0); + let expected_ranges = vec![BufferOffset(0)..BufferOffset(0)]; + let ranges = snapshot + .range_to_buffer_ranges(excerpt_2_start..excerpt_2_start) + .into_iter() + .map(|tup| tup.1) + .collect_vec(); + + assert_eq!( + ranges, expected_ranges, + "Zero-length range at excerpt boundary should return the excerpt at that point" + ); +} + #[gpui::test] async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) { let base_text = indoc!( diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 8424e163bbcd9c1fe4f0e89a5839a3277d13e1b1..3af1d5be32cf3a88bf75a9ab4fb586db9473977f 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -58,12 +58,6 @@ impl PathKey { } impl MultiBuffer { - pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option> { - let snapshot = self.snapshot(cx); - let excerpt = snapshot.excerpts_for_path(path).next()?; - self.buffer(excerpt.context.start.buffer_id) - } - pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option { let snapshot = self.snapshot(cx); let excerpt = snapshot.excerpts_for_path(path).next()?;