From 14f0606320fb22fdbe3830c9ff2e38242a7465d8 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 4 Dec 2025 00:55:36 -0500 Subject: [PATCH] fill in bufferdiff parts --- crates/buffer_diff/src/buffer_diff.rs | 141 +++++++++++++++++------- crates/editor/src/split.rs | 3 + crates/multi_buffer/src/multi_buffer.rs | 108 ++++++++++-------- 3 files changed, 171 insertions(+), 81 deletions(-) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 175031e77a65561b24e144b1fc2f839404d29193..e57fbb30dd0b28bfe84c2bebf7ba49dfad860c75 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -27,12 +27,21 @@ pub struct BufferDiff { secondary_diff: Option>, } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct BufferDiffSnapshot { inner: BufferDiffInner, secondary_diff: Option>, } +impl std::fmt::Debug for BufferDiffSnapshot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufferDiffSnapshot") + .field("inner", &self.inner) + .field("secondary_diff", &self.secondary_diff) + .finish() + } +} + #[derive(Clone)] struct BufferDiffInner { hunks: SumTree, @@ -338,9 +347,16 @@ impl BufferDiffSnapshot { pub fn hunks_intersecting_base_text_range<'a>( &'a self, range: Range, + main_buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator { - // FIXME - std::iter::empty() + let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); + let filter = move |summary: &DiffHunkSummary| { + let before_start = summary.diff_base_byte_range.end < range.start; + let after_end = summary.diff_base_byte_range.start > range.end; + !before_start && !after_end + }; + self.inner + .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart) } pub fn base_text(&self) -> &language::BufferSnapshot { @@ -623,15 +639,22 @@ impl BufferDiffInner { secondary: Option<&'a Self>, ) -> impl 'a + Iterator { let range = range.to_offset(buffer); + let filter = move |summary: &DiffHunkSummary| { + let summary_range = summary.buffer_range.to_offset(buffer); + let before_start = summary_range.end < range.start; + let after_end = summary_range.start > range.end; + !before_start && !after_end + }; + self.hunks_intersecting_range_impl(filter, buffer, secondary) + } - let mut cursor = self - .hunks - .filter::<_, DiffHunkSummary>(buffer, move |summary| { - let summary_range = summary.buffer_range.to_offset(buffer); - let before_start = summary_range.end < range.start; - let after_end = summary_range.start > range.end; - !before_start && !after_end - }); + fn hunks_intersecting_range_impl<'a>( + &'a self, + filter: impl 'a + Fn(&DiffHunkSummary) -> bool, + buffer: &'a text::BufferSnapshot, + secondary: Option<&'a Self>, + ) -> impl 'a + Iterator { + let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter); let anchor_iter = iter::from_fn(move || { cursor.next(); @@ -781,13 +804,19 @@ impl BufferDiffInner { }) } - fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option> { + fn compare( + &self, + old: &Self, + new_snapshot: &text::BufferSnapshot, + ) -> (Option>, Option>) { let mut new_cursor = self.hunks.cursor::<()>(new_snapshot); let mut old_cursor = old.hunks.cursor::<()>(new_snapshot); old_cursor.next(); new_cursor.next(); let mut start = None; let mut end = None; + let mut base_text_start = None; + let mut base_text_end = None; loop { match (new_cursor.item(), old_cursor.item()) { @@ -799,12 +828,15 @@ impl BufferDiffInner { { Ordering::Less => { start.get_or_insert(new_hunk.buffer_range.start); + base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start); end.replace(new_hunk.buffer_range.end); + base_text_end.get_or_insert(new_hunk.diff_base_byte_range.end); new_cursor.next(); } Ordering::Equal => { if new_hunk != old_hunk { start.get_or_insert(new_hunk.buffer_range.start); + base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start); if old_hunk .buffer_range .end @@ -812,8 +844,10 @@ impl BufferDiffInner { .is_ge() { end.replace(old_hunk.buffer_range.end); + base_text_end.replace(old_hunk.diff_base_byte_range.end); } else { end.replace(new_hunk.buffer_range.end); + base_text_end.replace(new_hunk.diff_base_byte_range.end); } } @@ -822,26 +856,37 @@ impl BufferDiffInner { } Ordering::Greater => { start.get_or_insert(old_hunk.buffer_range.start); + base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start); end.replace(old_hunk.buffer_range.end); + base_text_end.replace(old_hunk.diff_base_byte_range.end); old_cursor.next(); } } } (Some(new_hunk), None) => { start.get_or_insert(new_hunk.buffer_range.start); + base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start); end.replace(new_hunk.buffer_range.end); + base_text_end.replace(new_hunk.diff_base_byte_range.end); new_cursor.next(); } (None, Some(old_hunk)) => { start.get_or_insert(old_hunk.buffer_range.start); + base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start); end.replace(old_hunk.buffer_range.end); + base_text_end.replace(old_hunk.diff_base_byte_range.end); old_cursor.next(); } (None, None) => break, } } - start.zip(end).map(|(start, end)| start..end) + ( + start.zip(end).map(|(start, end)| start..end), + base_text_start + .zip(base_text_end) + .map(|(start, end)| start..end), + ) } } @@ -1135,7 +1180,7 @@ impl BufferDiff { }); cx.emit(BufferDiffEvent::DiffChanged { changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)), - base_text_changed_range: todo!(), + base_text_changed_range: Some(0..self.base_text().len()), }); } } @@ -1161,9 +1206,11 @@ impl BufferDiff { )); if let Some((first, last)) = hunks.first().zip(hunks.last()) { let changed_range = first.buffer_range.start..last.buffer_range.end; + let base_text_changed_range = + first.diff_base_byte_range.start..last.diff_base_byte_range.end; cx.emit(BufferDiffEvent::DiffChanged { changed_range: Some(changed_range), - base_text_changed_range: todo!(), + base_text_changed_range: Some(base_text_changed_range), }); } new_index_text @@ -1174,18 +1221,19 @@ impl BufferDiff { range: Range, buffer: &text::BufferSnapshot, cx: &App, - ) -> Option> { - let start = self + ) -> (Option>, Option>) { + let first_hunk = self .hunks_intersecting_range(range.clone(), buffer, cx) - .next()? - .buffer_range - .start; - let end = self - .hunks_intersecting_range_rev(range, buffer) - .next()? - .buffer_range - .end; - Some(start..end) + .next(); + let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next(); + let range = first_hunk + .as_ref() + .zip(last_hunk.as_ref()) + .map(|(first, last)| first.buffer_range.start..last.buffer_range.end); + let base_text_range = first_hunk + .zip(last_hunk) + .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end); + (range, base_text_range) } pub async fn update_diff( @@ -1247,9 +1295,9 @@ impl BufferDiff { let state = &mut self.inner; let new_state = new_snapshot.inner; - let (base_text_changed, mut changed_range) = + let (base_text_changed, (mut changed_range, mut base_text_changed_range)) = match (state.base_text_exists, new_state.base_text_exists) { - (false, false) => (true, None), + (false, false) => (true, (None, None)), (true, true) if state.base_text.remote_id() == new_state.base_text.remote_id() && state.base_text.syntax_update_count() @@ -1259,12 +1307,15 @@ impl BufferDiff { } _ => ( true, - Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)), + ( + Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)), + Some(0..new_state.base_text.len()), + ), ), }; if let Some(secondary_changed_range) = secondary_diff_change - && let Some(secondary_hunk_range) = + && let (Some(secondary_hunk_range), Some(secondary_base_range)) = self.range_to_hunk_range(secondary_changed_range, buffer, cx) { if let Some(range) = &mut changed_range { @@ -1273,6 +1324,13 @@ impl BufferDiff { } else { changed_range = Some(secondary_hunk_range); } + + if let Some(base_text_range) = &mut base_text_changed_range { + base_text_range.start = secondary_base_range.start.min(base_text_range.start); + base_text_range.end = secondary_base_range.end.max(base_text_range.end); + } else { + base_text_changed_range = Some(secondary_base_range); + } } let state = &mut self.inner; @@ -1288,13 +1346,22 @@ impl BufferDiff { } else { changed_range = Some(first.buffer_range.start..last.buffer_range.end); } + + if let Some(base_text_range) = &mut base_text_changed_range { + base_text_range.start = + base_text_range.start.min(first.diff_base_byte_range.start); + base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end); + } else { + base_text_changed_range = + Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end); + } } state.pending_hunks = SumTree::new(buffer); } cx.emit(BufferDiffEvent::DiffChanged { changed_range: changed_range.clone(), - base_text_changed_range: todo!(), + base_text_changed_range, }); changed_range } @@ -2163,7 +2230,7 @@ mod tests { let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx)); let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx); - let range = diff_1.inner.compare(&empty_diff.inner, &buffer).unwrap(); + let range = diff_1.inner.compare(&empty_diff.inner, &buffer).0.unwrap(); assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0)); // Edit does not affect the diff. @@ -2181,7 +2248,7 @@ mod tests { .unindent(), ); let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx); - assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer)); + assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer).0); // Edit turns a deletion hunk into a modification. buffer.edit_via_marked_text( @@ -2198,7 +2265,7 @@ mod tests { .unindent(), ); let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx); - let range = diff_3.inner.compare(&diff_2.inner, &buffer).unwrap(); + let range = diff_3.inner.compare(&diff_2.inner, &buffer).0.unwrap(); assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0)); // Edit turns a modification hunk into a deletion. @@ -2215,7 +2282,7 @@ mod tests { .unindent(), ); let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx); - let range = diff_4.inner.compare(&diff_3.inner, &buffer).unwrap(); + let range = diff_4.inner.compare(&diff_3.inner, &buffer).0.unwrap(); assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0)); // Edit introduces a new insertion hunk. @@ -2233,7 +2300,7 @@ mod tests { .unindent(), ); let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx); - let range = diff_5.inner.compare(&diff_4.inner, &buffer).unwrap(); + let range = diff_5.inner.compare(&diff_4.inner, &buffer).0.unwrap(); assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0)); // Edit removes a hunk. @@ -2251,7 +2318,7 @@ mod tests { .unindent(), ); let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx); - let range = diff_6.inner.compare(&diff_5.inner, &buffer).unwrap(); + let range = diff_6.inner.compare(&diff_5.inner, &buffer).0.unwrap(); assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0)); } diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index ca3aae839194deec1164b23b297068f4efc1aed2..0c2277c5bb144512e5c398ae057b67b9e5816933 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -8,6 +8,7 @@ use language::{Buffer, Capability, LanguageRegistry}; use multi_buffer::{Anchor, ExcerptRange, MultiBuffer, PathKey}; use project::Project; use rope::Point; +use text::OffsetRangeExt as _; use ui::{ App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render, Styled as _, Window, div, @@ -242,6 +243,8 @@ impl SplittableEditor { } } + // FIXME need add_diff management in here too + pub fn set_excerpts_for_path( &mut self, path: PathKey, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index a153c1830e8ad1576a5b98546b0a10283a7ac1e7..a7c60355969407ffe7c0a10e662c99d962876184 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -15,7 +15,7 @@ use buffer_diff::{ }; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; -use gpui::{App, Context, Entity, EntityId, EventEmitter}; +use gpui::{App, Context, Entity, EntityId, EventEmitter, WeakEntity}; use itertools::Itertools; use language::{ AutoindentMode, BracketMatch, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, @@ -514,7 +514,7 @@ struct DiffState { /// /// The [`DiffState`]s in a [`MultiBuffer`] must either be all inverted, or // all not inverted. - is_inverted: bool, + main_buffer: Option>, _subscription: gpui::Subscription, } @@ -522,7 +522,11 @@ impl DiffState { fn snapshot(&self, cx: &App) -> DiffStateSnapshot { DiffStateSnapshot { diff: self.diff.read(cx).snapshot(cx), - is_inverted: self.is_inverted, + main_buffer: self.main_buffer.as_ref().and_then(|main_buffer| { + main_buffer + .read_with(cx, |main_buffer, _| main_buffer.snapshot()) + .ok() + }), } } } @@ -530,7 +534,7 @@ impl DiffState { #[derive(Clone)] struct DiffStateSnapshot { diff: BufferDiffSnapshot, - is_inverted: bool, + main_buffer: Option, } impl std::ops::Deref for DiffStateSnapshot { @@ -558,37 +562,43 @@ impl DiffState { _ => {} }), diff, - is_inverted: false, + main_buffer: None, } } fn new_inverted( diff: Entity, base_text_buffer_id: BufferId, + main_buffer: Entity, cx: &mut Context, ) -> Self { + let main_buffer = main_buffer.downgrade(); DiffState { - _subscription: cx.subscribe(&diff, move |this, diff, event, cx| match event { - BufferDiffEvent::DiffChanged { - changed_range: _, - base_text_changed_range, - } => { - if let Some(base_text_changed_range) = base_text_changed_range.clone() { - this.inverted_buffer_diff_changed( - diff, - base_text_changed_range, - base_text_buffer_id, - cx, - ) + _subscription: cx.subscribe(&diff, { + let main_buffer = main_buffer.clone(); + move |this, diff, event, cx| match event { + BufferDiffEvent::DiffChanged { + changed_range: _, + base_text_changed_range, + } => { + if let Some(base_text_changed_range) = base_text_changed_range.clone() { + this.inverted_buffer_diff_changed( + diff, + base_text_changed_range, + base_text_buffer_id, + main_buffer.clone(), + cx, + ) + } + cx.emit(Event::BufferDiffChanged); } - cx.emit(Event::BufferDiffChanged); + // FIXME + BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx), + _ => {} } - // FIXME - BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx), - _ => {} }), diff, - is_inverted: true, + main_buffer: Some(main_buffer), } } } @@ -2267,7 +2277,7 @@ impl MultiBuffer { let buffer_id = diff.buffer_id; let diff = DiffStateSnapshot { diff: diff.snapshot(cx), - is_inverted: false, + main_buffer: None, }; self.snapshot.get_mut().diffs.insert(buffer_id, diff); } @@ -2276,12 +2286,15 @@ impl MultiBuffer { &mut self, base_text_buffer_id: BufferId, diff: Entity, + main_buffer: WeakEntity, cx: &mut Context, ) { let diff = diff.read(cx); let diff = DiffStateSnapshot { diff: diff.snapshot(cx), - is_inverted: true, + main_buffer: main_buffer + .update(cx, |main_buffer, _| main_buffer.snapshot()) + .ok(), }; self.snapshot .get_mut() @@ -2309,7 +2322,7 @@ impl MultiBuffer { let new_diff = DiffStateSnapshot { diff: diff.snapshot(cx), - is_inverted: false, + main_buffer: None, }; let mut snapshot = self.snapshot.get_mut(); let base_text_changed = snapshot @@ -2371,15 +2384,9 @@ impl MultiBuffer { diff: Entity, diff_change_range: Range, base_text_buffer_id: BufferId, + main_buffer: WeakEntity, cx: &mut Context, ) { - // FIXME move this to the level of the splittableeditor - // if base_text_changed && let Some(buffer_state) = self.buffers.get(&base_text_buffer_id) { - // buffer_state.buffer.update(cx, |buffer, cx| { - // // FIXME use the rope directly - // buffer.set_text(diff.read(cx).base_text().text(), cx); - // }); - // } self.sync_mut(cx); let diff = diff.read(cx); @@ -2390,7 +2397,9 @@ impl MultiBuffer { let new_diff = DiffStateSnapshot { diff: diff.snapshot(cx), - is_inverted: true, + main_buffer: main_buffer + .update(cx, |main_buffer, _| main_buffer.snapshot()) + .ok(), }; let mut snapshot = self.snapshot.get_mut(); let base_text_changed = snapshot @@ -2601,7 +2610,7 @@ impl MultiBuffer { } pub fn add_diff(&mut self, diff: Entity, cx: &mut Context) { - debug_assert!(self.diffs.values().all(|diff| !diff.is_inverted)); + debug_assert!(self.diffs.values().all(|diff| diff.main_buffer.is_none())); let buffer_id = diff.read(cx).buffer_id; self.buffer_diff_changed( @@ -2617,15 +2626,22 @@ impl MultiBuffer { &mut self, base_text_buffer_id: BufferId, diff: Entity, + main_buffer: Entity, cx: &mut Context, ) { - debug_assert!(self.diffs.values().all(|diff| diff.is_inverted)); + debug_assert!(self.diffs.values().all(|diff| diff.main_buffer.is_some())); let diff_change_range = 0..diff.read(cx).base_text().len(); - self.inverted_buffer_diff_changed(diff.clone(), diff_change_range, base_text_buffer_id, cx); + self.inverted_buffer_diff_changed( + diff.clone(), + diff_change_range, + base_text_buffer_id, + main_buffer.downgrade(), + cx, + ); self.diffs.insert( base_text_buffer_id, - DiffState::new_inverted(diff, base_text_buffer_id, cx), + DiffState::new_inverted(diff, base_text_buffer_id, main_buffer, cx), ); } @@ -3300,10 +3316,11 @@ impl MultiBuffer { excerpt_buffer_start + edit.new.end.saturating_sub(excerpt_start); let edit_buffer_end = edit_buffer_end.min(excerpt_buffer_end); - if diff.is_inverted { - for hunk in - diff.hunks_intersecting_base_text_range(edit_buffer_start..edit_buffer_end) - { + if let Some(main_buffer) = &diff.main_buffer { + for hunk in diff.hunks_intersecting_base_text_range( + edit_buffer_start..edit_buffer_end, + main_buffer, + ) { let hunk_buffer_range = hunk.diff_base_byte_range.clone(); if hunk_buffer_range.start < excerpt_buffer_start { log::trace!("skipping hunk that starts before excerpt"); @@ -3838,12 +3855,15 @@ impl MultiBufferSnapshot { self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| { let diff = self.diffs.get(&buffer.remote_id())?; let iter: Box> = - if diff.is_inverted { + if let Some(main_buffer) = &diff.main_buffer { let buffer_start = buffer.point_to_offset(buffer_range.start); let buffer_end = buffer.point_to_offset(buffer_range.end); Box::new( - diff.hunks_intersecting_base_text_range(buffer_start..buffer_end) - .map(move |hunk| (hunk, buffer, true)), + diff.hunks_intersecting_base_text_range( + buffer_start..buffer_end, + main_buffer, + ) + .map(move |hunk| (hunk, buffer, true)), ) } else { let buffer_start = buffer.anchor_before(buffer_range.start);