Detailed changes
@@ -1056,7 +1056,6 @@ impl Iterator for WrapRows<'_> {
RowInfo {
buffer_id: None,
buffer_row: None,
- base_text_row: None,
multibuffer_row: None,
diff_status,
expand_info: None,
@@ -35,9 +35,7 @@ use language_settings::Formatter;
use languages::markdown_lang;
use languages::rust_lang;
use lsp::CompletionParams;
-use multi_buffer::{
- IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
-};
+use multi_buffer::{IndentGuide, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{
@@ -28375,207 +28373,207 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
"});
}
-#[gpui::test]
-async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
- init_test(cx, |_| {});
- let mut leader_cx = EditorTestContext::new(cx).await;
-
- let diff_base = indoc!(
- r#"
- one
- two
- three
- four
- five
- six
- "#
- );
-
- let initial_state = indoc!(
- r#"
- ˇone
- two
- THREE
- four
- five
- six
- "#
- );
-
- leader_cx.set_state(initial_state);
-
- leader_cx.set_head_text(&diff_base);
- leader_cx.run_until_parked();
-
- let follower = leader_cx.update_multibuffer(|leader, cx| {
- leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
- leader.set_all_diff_hunks_expanded(cx);
- leader.get_or_create_follower(cx)
- });
- follower.update(cx, |follower, cx| {
- follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
- follower.set_all_diff_hunks_expanded(cx);
- });
-
- let follower_editor =
- leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
- // leader_cx.window.focus(&follower_editor.focus_handle(cx));
-
- let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
- cx.run_until_parked();
-
- leader_cx.assert_editor_state(initial_state);
- follower_cx.assert_editor_state(indoc! {
- r#"
- ˇone
- two
- three
- four
- five
- six
- "#
- });
-
- follower_cx.editor(|editor, _window, cx| {
- assert!(editor.read_only(cx));
- });
-
- leader_cx.update_editor(|editor, _window, cx| {
- editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
- });
- cx.run_until_parked();
-
- leader_cx.assert_editor_state(indoc! {
- r#"
- ˇone
- two
- THREE
- four
- FIVE
- six
- "#
- });
-
- follower_cx.assert_editor_state(indoc! {
- r#"
- ˇone
- two
- three
- four
- five
- six
- "#
- });
-
- leader_cx.update_editor(|editor, _window, cx| {
- editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
- });
- cx.run_until_parked();
-
- leader_cx.assert_editor_state(indoc! {
- r#"
- ˇone
- two
- THREE
- four
- FIVE
- six
- SEVEN"#
- });
-
- follower_cx.assert_editor_state(indoc! {
- r#"
- ˇone
- two
- three
- four
- five
- six
- "#
- });
-
- leader_cx.update_editor(|editor, window, cx| {
- editor.move_down(&MoveDown, window, cx);
- editor.refresh_selected_text_highlights(true, window, cx);
- });
- leader_cx.run_until_parked();
-}
-
-#[gpui::test]
-async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
- init_test(cx, |_| {});
- let base_text = "base\n";
- let buffer_text = "buffer\n";
-
- let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
- let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
-
- let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
- let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
- let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
- let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
-
- let leader = cx.new(|cx| {
- let mut leader = MultiBuffer::new(Capability::ReadWrite);
- leader.set_all_diff_hunks_expanded(cx);
- leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
- leader
- });
- let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
- follower.update(cx, |follower, _| {
- follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
- });
-
- leader.update(cx, |leader, cx| {
- leader.insert_excerpts_after(
- ExcerptId::min(),
- extra_buffer_2.clone(),
- vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
- cx,
- );
- leader.add_diff(extra_diff_2.clone(), cx);
-
- leader.insert_excerpts_after(
- ExcerptId::min(),
- extra_buffer_1.clone(),
- vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
- cx,
- );
- leader.add_diff(extra_diff_1.clone(), cx);
-
- leader.insert_excerpts_after(
- ExcerptId::min(),
- buffer1.clone(),
- vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
- cx,
- );
- leader.add_diff(diff1.clone(), cx);
- });
-
- cx.run_until_parked();
- let mut cx = cx.add_empty_window();
-
- let leader_editor = cx
- .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
- let follower_editor = cx.new_window_entity(|window, cx| {
- Editor::for_multibuffer(follower.clone(), None, window, cx)
- });
-
- let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
- leader_cx.assert_editor_state(indoc! {"
- ˇbuffer
-
- dummy text 1
-
- dummy text 2
- "});
- let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
- follower_cx.assert_editor_state(indoc! {"
- ˇbase
-
-
- "});
-}
+// FIXME restore these tests in some form
+// #[gpui::test]
+// async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
+// init_test(cx, |_| {});
+// let mut leader_cx = EditorTestContext::new(cx).await;
+
+// let diff_base = indoc!(
+// r#"
+// one
+// two
+// three
+// four
+// five
+// six
+// "#
+// );
+
+// let initial_state = indoc!(
+// r#"
+// ˇone
+// two
+// THREE
+// four
+// five
+// six
+// "#
+// );
+
+// leader_cx.set_state(initial_state);
+
+// leader_cx.set_head_text(&diff_base);
+// leader_cx.run_until_parked();
+
+// let follower = leader_cx.update_multibuffer(|leader, cx| {
+// leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
+// leader.set_all_diff_hunks_expanded(cx);
+// leader.get_or_create_follower(cx)
+// });
+// follower.update(cx, |follower, cx| {
+// follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
+// follower.set_all_diff_hunks_expanded(cx);
+// });
+
+// let follower_editor =
+// leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
+// // leader_cx.window.focus(&follower_editor.focus_handle(cx));
+
+// let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
+// cx.run_until_parked();
+
+// leader_cx.assert_editor_state(initial_state);
+// follower_cx.assert_editor_state(indoc! {
+// r#"
+// ˇone
+// two
+// three
+// four
+// five
+// six
+// "#
+// });
+
+// follower_cx.editor(|editor, _window, cx| {
+// assert!(editor.read_only(cx));
+// });
+
+// leader_cx.update_editor(|editor, _window, cx| {
+// editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
+// });
+// cx.run_until_parked();
+
+// leader_cx.assert_editor_state(indoc! {
+// r#"
+// ˇone
+// two
+// THREE
+// four
+// FIVE
+// six
+// "#
+// });
+
+// follower_cx.assert_editor_state(indoc! {
+// r#"
+// ˇone
+// two
+// three
+// four
+// five
+// six
+// "#
+// });
+
+// leader_cx.update_editor(|editor, _window, cx| {
+// editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
+// });
+// cx.run_until_parked();
+
+// leader_cx.assert_editor_state(indoc! {
+// r#"
+// ˇone
+// two
+// THREE
+// four
+// FIVE
+// six
+// SEVEN"#
+// });
+
+// follower_cx.assert_editor_state(indoc! {
+// r#"
+// ˇone
+// two
+// three
+// four
+// five
+// six
+// "#
+// });
+
+// leader_cx.update_editor(|editor, window, cx| {
+// editor.move_down(&MoveDown, window, cx);
+// editor.refresh_selected_text_highlights(true, window, cx);
+// });
+// leader_cx.run_until_parked();
+// }
+
+// #[gpui::test]
+// async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
+// init_test(cx, |_| {});
+// let base_text = "base\n";
+// let buffer_text = "buffer\n";
+
+// let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
+// let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
+
+// let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
+// let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
+// let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
+// let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
+
+// let leader = cx.new(|cx| {
+// let mut leader = MultiBuffer::new(Capability::ReadWrite);
+// leader.set_all_diff_hunks_expanded(cx);
+// leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
+// leader
+// });
+// let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
+// follower.update(cx, |follower, _| {
+// follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
+// });
+
+// leader.update(cx, |leader, cx| {
+// leader.insert_excerpts_after(
+// ExcerptId::min(),
+// extra_buffer_2.clone(),
+// vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+// cx,
+// );
+// leader.add_diff(extra_diff_2.clone(), cx);
+
+// leader.insert_excerpts_after(
+// ExcerptId::min(),
+// extra_buffer_1.clone(),
+// vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+// cx,
+// );
+// leader.add_diff(extra_diff_1.clone(), cx);
+
+// leader.insert_excerpts_after(
+// ExcerptId::min(),
+// buffer1.clone(),
+// vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+// cx,
+// );
+// leader.add_diff(diff1.clone(), cx);
+// });
+
+// cx.run_until_parked();
+// let mut cx = cx.add_empty_window();
+
+// let leader_editor = cx
+// .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
+// let follower_editor = cx.new_window_entity(|window, cx| {
+// Editor::for_multibuffer(follower.clone(), None, window, cx)
+// });
+
+// let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
+// leader_cx.assert_editor_state(indoc! {"
+// ˇbuffer
+
+// dummy text 1
+
+// dummy text 2
+// "});
+// let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
+// follower_cx.assert_editor_state(indoc! {"
+// ˇbase
+
+// "});
+// }
#[gpui::test]
async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
@@ -3274,8 +3274,6 @@ impl EditorElement {
line_number.clear();
let non_relative_number = if relative.wrapped() {
row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
- } else if self.editor.read(cx).use_base_text_line_numbers {
- row_info.base_text_row?.0 + 1
} else {
row_info.buffer_row? + 1
};
@@ -1,9 +1,16 @@
+use std::ops::Range;
+
+use buffer_diff::{BufferDiff, BufferDiffSnapshot};
+use collections::HashMap;
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{
Action, AppContext as _, Entity, EventEmitter, Focusable, NoAction, Subscription, WeakEntity,
};
-use multi_buffer::{MultiBuffer, MultiBufferFilterMode};
+use language::{Buffer, BufferSnapshot, Capability};
+use multi_buffer::{Anchor, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey};
use project::Project;
+use rope::Point;
+use text::{BufferId, OffsetRangeExt as _};
use ui::{
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
Styled as _, Window, div,
@@ -133,25 +140,86 @@ impl SplittableEditor {
return;
};
let project = workspace.read(cx).project().clone();
- let follower = self.primary_editor.update(cx, |primary, cx| {
- primary.buffer().update(cx, |buffer, cx| {
- let follower = buffer.get_or_create_follower(cx);
- buffer.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
- follower
- })
- });
- follower.update(cx, |follower, _| {
- follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
- });
- let secondary_editor = workspace.update(cx, |workspace, cx| {
- cx.new(|cx| {
- let mut editor = Editor::for_multibuffer(follower, Some(project), window, cx);
- // TODO(split-diff) this should be at the multibuffer level
- editor.set_use_base_text_line_numbers(true, cx);
- editor.added_to_workspace(workspace, window, cx);
- editor
+ let language_registry = project.read(cx).languages().clone();
+
+ let primary_multibuffer = self.primary_editor.read(cx).buffer().read(cx);
+
+ let base_text_buffers_by_main_buffer_id: HashMap<
+ BufferId,
+ (Entity<Buffer>, BufferDiffSnapshot),
+ > = primary_multibuffer
+ .all_buffer_ids_iter()
+ .filter_map(|main_buffer_id| {
+ let diff = primary_multibuffer.diff_for(main_buffer_id)?;
+ let base_text_buffer = cx.new(|cx| {
+ let base_text = diff.read(cx).base_text();
+ let mut buffer = Buffer::local_normalized(
+ base_text.as_rope().clone(),
+ base_text.line_ending(),
+ cx,
+ );
+ buffer.set_language(base_text.language().cloned(), cx);
+ buffer.set_language_registry(language_registry.clone());
+ buffer
+ });
+ Some((
+ main_buffer_id,
+ (base_text_buffer, diff.read(cx).snapshot(cx)),
+ ))
})
+ .collect();
+ let snapshot = primary_multibuffer.snapshot(cx);
+ let mut excerpt_ranges_by_base_buffer: HashMap<
+ Entity<Buffer>,
+ (PathKey, Vec<ExcerptRange<Point>>),
+ > = HashMap::default();
+ for (path_key, excerpt_id) in primary_multibuffer.excerpts_with_paths() {
+ let main_buffer = snapshot.buffer_for_excerpt(*excerpt_id).unwrap();
+ let excerpt_range = snapshot.excerpt_range_for_excerpt(*excerpt_id).unwrap();
+ let (base_text_buffer, diff) = base_text_buffers_by_main_buffer_id
+ .get(&main_buffer.remote_id())
+ .unwrap();
+ let point_to_base_text_point = |point: Point| {
+ let row = diff.row_to_base_text_row(point.row, &main_buffer);
+ let column = diff.base_text().line_len(row);
+ Point::new(row, column)
+ };
+ let primary = excerpt_range.primary.to_point(&main_buffer);
+ let context = excerpt_range.context.to_point(&main_buffer);
+ let translated_range = ExcerptRange {
+ primary: point_to_base_text_point(primary.start)
+ ..point_to_base_text_point(primary.end),
+ context: point_to_base_text_point(context.start)
+ ..point_to_base_text_point(context.end),
+ };
+ excerpt_ranges_by_base_buffer
+ .entry(base_text_buffer.clone())
+ .or_insert((path_key.clone(), Vec::new()))
+ .1
+ .push(translated_range);
+ }
+
+ let secondary_multibuffer = cx.new(|cx| {
+ let mut multibuffer = MultiBuffer::new(Capability::ReadOnly);
+ for (base_text_buffer, (path_key, ranges)) in excerpt_ranges_by_base_buffer {
+ let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();
+ multibuffer.update_path_excerpts(
+ path_key,
+ base_text_buffer,
+ &base_text_buffer_snapshot,
+ ranges,
+ cx,
+ );
+ }
+ multibuffer
});
+ let secondary_editor =
+ cx.new(|cx| Editor::for_multibuffer(secondary_multibuffer, Some(project), window, cx));
+
+ // FIXME
+ // - have to subscribe to the diffs to update the base text buffers (and handle language changed I think?)
+ // - implement SplittableEditor::set_excerpts_for_path
+
let secondary_pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.downgrade(),
@@ -186,12 +254,23 @@ impl SplittableEditor {
cx.emit(event.clone())
}),
];
- self.secondary = Some(SecondaryEditor {
+ let mut secondary = SecondaryEditor {
editor: secondary_editor,
pane: secondary_pane.clone(),
has_latest_selection: false,
_subscriptions: subscriptions,
- });
+ };
+ for (path_key, diff, original_range, original_buffer) in whatever {
+ secondary.sync_path_excerpts_for_buffer(
+ path_key,
+ diff,
+ original_range,
+ original_buffer,
+ cx,
+ );
+ }
+ self.secondary = Some(secondary);
+
let primary_pane = self.panes.first_pane();
self.panes
.split(&primary_pane, &secondary_pane, SplitDirection::Left)
@@ -228,6 +307,27 @@ impl SplittableEditor {
});
}
}
+
+ pub fn set_excerpts_for_path(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ ranges: impl IntoIterator<Item = Range<Point>>,
+ context_line_count: u32,
+ cx: &mut Context<Self>,
+ ) -> (Vec<Range<Anchor>>, bool) {
+ let (anchors, added_a_new_excerpt) =
+ self.primary_editor
+ .read(cx)
+ .buffer()
+ .update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(path, buffer, ranges, context_line_count, cx)
+ });
+ if let Some(secondary) = &mut self.secondary {
+ secondary.sync_path_excerpts_for_buffer(cx);
+ }
+ (anchors, added_a_new_excerpt)
+ }
}
impl EventEmitter<EditorEvent> for SplittableEditor {}
@@ -265,3 +365,56 @@ impl Render for SplittableEditor {
.child(inner)
}
}
+
+impl SecondaryEditor {
+ fn sync_path_excerpts_for_buffer(
+ &mut self,
+ path_key: PathKey,
+ main_buffer: &BufferSnapshot,
+ primary_multibuffer: &MultiBuffer,
+ cx: &mut App,
+ ) {
+ let diff = primary_multibuffer
+ .diff_for(main_buffer.remote_id())
+ .unwrap();
+ let diff = diff.read(cx).snapshot(cx);
+ // option 1: hold onto the base text buffers in splittable editor so that we can check whether they exist yet
+ // option 2: have the multibuffer continue to be fully responsible for holding the base text buffers; then need to be able to get a buffer out of the multibuffer based on a pathkey
+ let base_text_buffer = self.editor.update(cx, |editor, cx| {
+ editor
+ .buffer()
+ .update(cx, |buffer, cx| buffer.buffer_for_path_key)
+ });
+ let new = primary_multibuffer
+ .excerpts_for_buffer(main_buffer.remote_id(), cx)
+ .into_iter()
+ .map(|(excerpt_id, excerpt_range)| {
+ let point_to_base_text_point = |point: Point| {
+ let row = diff.row_to_base_text_row(point.row, &main_buffer);
+ let column = diff.base_text().line_len(row);
+ Point::new(row, column)
+ };
+ let primary = excerpt_range.primary.to_point(&main_buffer);
+ let context = excerpt_range.context.to_point(&main_buffer);
+ ExcerptRange {
+ primary: point_to_base_text_point(primary.start)
+ ..point_to_base_text_point(primary.end),
+ context: point_to_base_text_point(context.start)
+ ..point_to_base_text_point(context.end),
+ }
+ })
+ .collect();
+
+ self.editor.update(cx, |editor, cx| {
+ editor.buffer().update(cx, |buffer, cx| {
+ buffer.update_path_excerpts(
+ path_key,
+ base_text_buffer,
+ base_text_buffer_snapshot,
+ new,
+ cx,
+ )
+ })
+ });
+ }
+}
@@ -10,8 +10,8 @@ pub use anchor::{Anchor, AnchorRangeExt};
use anyhow::{Result, anyhow};
use buffer_diff::{
- BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkSecondaryStatus, DiffHunkStatus,
- DiffHunkStatusKind,
+ BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunk, DiffHunkSecondaryStatus,
+ DiffHunkStatus, DiffHunkStatusKind,
};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
@@ -87,8 +87,6 @@ pub struct MultiBuffer {
/// The writing capability of the multi-buffer.
capability: Capability,
buffer_changed_since_sync: Rc<Cell<bool>>,
- follower: Option<Entity<MultiBuffer>>,
- base_text_buffers_by_main_buffer_id: HashMap<BufferId, Entity<Buffer>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -141,6 +139,8 @@ pub struct MultiBufferDiffHunk {
pub diff_base_byte_range: Range<BufferOffset>,
/// Whether or not this hunk also appears in the 'secondary diff'.
pub secondary_status: DiffHunkSecondaryStatus,
+ /// The word diffs for this hunk.
+ pub word_diffs: Vec<Range<MultiBufferOffset>>,
}
impl MultiBufferDiffHunk {
@@ -508,17 +508,31 @@ struct BufferState {
struct DiffState {
diff: Entity<BufferDiff>,
+ /// Whether the [`MultiBuffer`] this is associated with is inverted (i.e.
+ /// showing additions as deletions). This is used in the side-by-side diff
+ /// view.
+ ///
+ /// The [`DiffState`]s in a [`MultiBuffer`] must either be all inverted, or
+ // all not inverted.
is_inverted: bool,
_subscription: gpui::Subscription,
}
+impl DiffState {
+ fn snapshot(&self, cx: &App) -> DiffStateSnapshot {
+ DiffStateSnapshot {
+ diff: self.diff.read(cx).snapshot(cx),
+ is_inverted: self.is_inverted,
+ }
+ }
+}
+
#[derive(Clone)]
struct DiffStateSnapshot {
diff: BufferDiffSnapshot,
is_inverted: bool,
}
-// FIXME
impl std::ops::Deref for DiffStateSnapshot {
type Target = BufferDiffSnapshot;
@@ -550,7 +564,7 @@ impl DiffState {
fn new_inverted(
diff: Entity<BufferDiff>,
- base_text_buffer: Entity<Buffer>,
+ base_text_buffer_id: BufferId,
cx: &mut Context<MultiBuffer>,
) -> Self {
DiffState {
@@ -563,7 +577,7 @@ impl DiffState {
this.inverted_buffer_diff_changed(
diff,
base_text_changed_range,
- base_text_buffer.clone(),
+ base_text_buffer_id,
cx,
)
}
@@ -577,10 +591,6 @@ impl DiffState {
is_inverted: true,
}
}
-
- fn snapshot(&self, cx: &App) -> DiffStateSnapshot {
- todo!()
- }
}
/// The contents of a [`MultiBuffer`] at a single point in time.
@@ -935,7 +945,7 @@ pub struct MultiBufferChunks<'a> {
excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>,
diff_transforms:
Cursor<'a, 'static, DiffTransform, Dimensions<MultiBufferOffset, ExcerptOffset>>,
- diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
+ diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
buffer_chunk: Option<Chunk<'a>>,
range: Range<MultiBufferOffset>,
@@ -995,7 +1005,7 @@ impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for Diff
struct MultiBufferCursor<'a, MBD, BD> {
excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<MBD>>,
diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<MBD>>,
- diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
+ diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
cached_region: Option<MultiBufferRegion<'a, MBD, BD>>,
}
@@ -2252,10 +2262,30 @@ impl MultiBuffer {
fn buffer_diff_language_changed(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
let diff = diff.read(cx);
let buffer_id = diff.buffer_id;
- let diff = diff.snapshot(cx);
+ let diff = DiffStateSnapshot {
+ diff: diff.snapshot(cx),
+ is_inverted: false,
+ };
self.snapshot.get_mut().diffs.insert(buffer_id, diff);
}
+ fn inverted_buffer_diff_language_changed(
+ &mut self,
+ base_text_buffer_id: BufferId,
+ diff: Entity<BufferDiff>,
+ cx: &mut Context<Self>,
+ ) {
+ let diff = diff.read(cx);
+ let diff = DiffStateSnapshot {
+ diff: diff.snapshot(cx),
+ is_inverted: true,
+ };
+ self.snapshot
+ .get_mut()
+ .diffs
+ .insert(base_text_buffer_id, diff);
+ }
+
fn buffer_diff_changed(
&mut self,
diff: Entity<BufferDiff>,
@@ -2274,7 +2304,10 @@ impl MultiBuffer {
let buffer = buffer_state.buffer.read(cx);
let diff_change_range = range.to_offset(buffer);
- let new_diff = diff.snapshot(cx);
+ let new_diff = DiffStateSnapshot {
+ diff: diff.snapshot(cx),
+ is_inverted: false,
+ };
let mut snapshot = self.snapshot.get_mut();
let base_text_changed = snapshot
.diffs
@@ -2330,41 +2363,41 @@ impl MultiBuffer {
});
}
- // FIXME should take main_text_buffer_id right?
fn inverted_buffer_diff_changed(
&mut self,
diff: Entity<BufferDiff>,
diff_change_range: Range<usize>,
- base_text_buffer: Entity<Buffer>,
+ base_text_buffer_id: BufferId,
cx: &mut Context<Self>,
) {
- let base_text_buffer_id = base_text_buffer.read(cx).remote_id();
- let snapshot = self.snapshot.get_mut();
- let new_diff = diff.read(cx).snapshot(cx);
- let base_text_changed = snapshot
- .diffs
- .get(&base_text_buffer_id)
- .is_none_or(|old_diff| !new_diff.base_texts_eq(old_diff));
- if base_text_changed {
- base_text_buffer.update(cx, |buffer, cx| {
- // FIXME use the rope directly
- buffer.set_text(diff.read(cx).base_text().text(), cx);
- });
- }
+ // 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);
let Some(buffer_state) = self.buffers.get(&base_text_buffer_id) else {
return;
};
self.buffer_changed_since_sync.replace(true);
+
+ let new_diff = DiffStateSnapshot {
+ diff: diff.snapshot(cx),
+ is_inverted: true,
+ };
let mut snapshot = self.snapshot.get_mut();
- snapshot.diffs.insert_or_replace(
- base_text_buffer_id,
- DiffStateSnapshot {
- diff: new_diff,
- is_inverted: true,
- },
- );
+ let base_text_changed = snapshot
+ .diffs
+ .get(&base_text_buffer_id)
+ .is_none_or(|old_diff| !new_diff.base_texts_eq(old_diff));
+
+ snapshot
+ .diffs
+ .insert_or_replace(base_text_buffer_id, new_diff);
let mut excerpt_edits = Vec::new();
for locator in &buffer_state.excerpts {
@@ -2413,15 +2446,20 @@ impl MultiBuffer {
});
}
+ pub fn all_buffers_iter(&self) -> impl Iterator<Item = Entity<Buffer>> {
+ self.buffers.values().map(|state| state.buffer.clone())
+ }
+
pub fn all_buffers(&self) -> HashSet<Entity<Buffer>> {
- self.buffers
- .values()
- .map(|state| state.buffer.clone())
- .collect()
+ self.all_buffers_iter().collect()
+ }
+
+ pub fn all_buffer_ids_iter(&self) -> impl Iterator<Item = BufferId> {
+ self.buffers.keys().copied()
}
pub fn all_buffer_ids(&self) -> Vec<BufferId> {
- self.buffers.keys().copied().collect()
+ self.all_buffer_ids_iter().collect()
}
pub fn buffer(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> {
@@ -2560,6 +2598,8 @@ impl MultiBuffer {
}
pub fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
+ debug_assert!(self.diffs.values().all(|diff| !diff.is_inverted));
+
let buffer_id = diff.read(cx).buffer_id;
self.buffer_diff_changed(
diff.clone(),
@@ -2568,23 +2608,22 @@ impl MultiBuffer {
);
self.diffs
.insert(buffer_id, DiffState::new(diff.clone(), cx));
+ }
- if let Some(follower) = &self.follower {
- follower.update(cx, |follower, cx| {
- let diff_change_range = 0..diff.read(cx).base_text().len();
- let base_text_buffer: Entity<Buffer> = create_base_text_buffer(&diff);
- follower.inverted_buffer_diff_changed(
- diff,
- diff_change_range,
- base_text_buffer.clone(),
- cx,
- );
- follower.diffs.insert(
- base_text_buffer.read(cx).remote_id(),
- DiffState::new_inverted(diff, base_text_buffer, cx),
- );
- });
- }
+ pub fn add_inverted_diff(
+ &mut self,
+ base_text_buffer_id: BufferId,
+ diff: Entity<BufferDiff>,
+ cx: &mut Context<Self>,
+ ) {
+ debug_assert!(self.diffs.values().all(|diff| diff.is_inverted));
+
+ 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.diffs.insert(
+ base_text_buffer_id,
+ DiffState::new_inverted(diff, base_text_buffer_id, cx),
+ );
}
pub fn diff_for(&self, buffer_id: BufferId) -> Option<Entity<BufferDiff>> {
@@ -2997,7 +3036,7 @@ impl MultiBuffer {
for (id, diff) in diffs.iter() {
if buffer_diff.get(id).is_none() {
- buffer_diff.insert(*id, diff.diff.read(cx).snapshot(cx));
+ buffer_diff.insert(*id, diff.snapshot(cx));
}
}
@@ -3343,6 +3382,7 @@ impl MultiBuffer {
excerpt.id
);
+ // FIXME don't push the deleted region if this is the RHS of a split
if !hunk.diff_base_byte_range.is_empty()
&& hunk_buffer_range.start >= edit_buffer_start
&& hunk_buffer_range.start <= excerpt_buffer_end
@@ -3788,19 +3828,35 @@ impl MultiBufferSnapshot {
let query_range = range.start.to_point(self)..range.end.to_point(self);
self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| {
let diff = self.diffs.get(&buffer.remote_id())?;
- let buffer_start = buffer.anchor_before(buffer_range.start);
- let buffer_end = buffer.anchor_after(buffer_range.end);
- Some(
- diff.hunks_intersecting_range(buffer_start..buffer_end, buffer)
- .filter_map(|hunk| {
- if hunk.is_created_file() && !self.all_diff_hunks_expanded {
- return None;
- }
- Some((hunk.range.clone(), hunk))
- }),
- )
+ let iter: Box<dyn Iterator<Item = (DiffHunk, &BufferSnapshot, bool)>> =
+ if diff.is_inverted {
+ 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)),
+ )
+ } else {
+ let buffer_start = buffer.anchor_before(buffer_range.start);
+ let buffer_end = buffer.anchor_after(buffer_range.end);
+ Box::new(
+ diff.hunks_intersecting_range(buffer_start..buffer_end, buffer)
+ .map(move |hunk| (hunk, buffer, false)),
+ )
+ };
+ Some(iter.filter_map(|(hunk, buffer, is_inverted)| {
+ if hunk.is_created_file() && !self.all_diff_hunks_expanded {
+ return None;
+ }
+ let range = if is_inverted {
+ hunk.diff_base_byte_range.to_point(&buffer)
+ } else {
+ hunk.range.clone()
+ };
+ Some((range, (hunk, is_inverted)))
+ }))
})
- .filter_map(move |(range, hunk, excerpt)| {
+ .filter_map(move |(range, (hunk, is_inverted), excerpt)| {
if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty()
{
return None;
@@ -3810,11 +3866,37 @@ impl MultiBufferSnapshot {
} else {
range.end.row + 1
};
+
+ let word_diffs = (!hunk.base_word_diffs.is_empty()
+ || !hunk.buffer_word_diffs.is_empty())
+ .then(|| {
+ let hunk_start_offset =
+ Anchor::in_buffer(excerpt.id, hunk.buffer_range.start).to_offset(self);
+
+ hunk.base_word_diffs
+ .iter()
+ .map(|diff| hunk_start_offset + diff.start..hunk_start_offset + diff.end)
+ .chain(
+ hunk.buffer_word_diffs
+ .into_iter()
+ .map(|diff| Anchor::range_in_buffer(excerpt.id, diff).to_offset(self)),
+ )
+ .collect()
+ })
+ .unwrap_or_default();
+
+ let buffer_range = if is_inverted {
+ excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start)
+ ..excerpt.buffer.anchor_before(hunk.diff_base_byte_range.end)
+ } else {
+ hunk.buffer_range.clone()
+ };
Some(MultiBufferDiffHunk {
row_range: MultiBufferRow(range.start.row)..MultiBufferRow(end_row),
buffer_id: excerpt.buffer_id,
excerpt_id: excerpt.id,
- buffer_range: hunk.buffer_range.clone(),
+ buffer_range,
+ word_diffs,
diff_base_byte_range: BufferOffset(hunk.diff_base_byte_range.start)
..BufferOffset(hunk.diff_base_byte_range.end),
secondary_status: hunk.secondary_status,
@@ -4120,6 +4202,7 @@ impl MultiBufferSnapshot {
})
}
+ // FIXME need to make this work with inverted diffs
pub fn diff_hunk_before<T: ToOffset>(&self, position: T) -> Option<MultiBufferRow> {
let offset = position.to_offset(self);
@@ -5340,6 +5423,13 @@ impl MultiBufferSnapshot {
Some(self.excerpt(excerpt_id)?.range.context.clone())
}
+ pub fn excerpt_range_for_excerpt(
+ &self,
+ excerpt_id: ExcerptId,
+ ) -> Option<ExcerptRange<text::Anchor>> {
+ Some(self.excerpt(excerpt_id)?.range.clone())
+ }
+
pub fn can_resolve(&self, anchor: &Anchor) -> bool {
if anchor.is_min() || anchor.is_max() {
// todo(lw): should be `!self.is_empty()`
@@ -6454,10 +6544,6 @@ impl MultiBufferSnapshot {
self.show_headers
}
- pub fn diff_for_buffer_id(&self, buffer_id: BufferId) -> Option<&BufferDiffSnapshot> {
- self.diffs.get(&buffer_id)
- }
-
/// Visually annotates a position or range with the `Debug` representation of a value. The
/// callsite of this function is used as a key - previous annotations will be removed.
#[cfg(debug_assertions)]
@@ -29,7 +29,6 @@ fn test_empty_singleton(cx: &mut App) {
[RowInfo {
buffer_id: Some(buffer_id),
buffer_row: Some(0),
- base_text_row: None,
multibuffer_row: Some(MultiBufferRow(0)),
diff_status: None,
expand_info: None,
@@ -2511,38 +2510,6 @@ impl ReferenceMultibuffer {
.iter()
.find(|e| e.id == region.excerpt_id.unwrap())
.map(|e| e.buffer.clone());
- let base_text_row = match region.status {
- None => Some(
- main_buffer
- .as_ref()
- .map(|main_buffer| {
- let diff = self
- .diffs
- .get(&main_buffer.read(cx).remote_id())
- .unwrap();
- let buffer_row = buffer_row.unwrap();
- BaseTextRow(
- diff.read(cx).snapshot(cx).row_to_base_text_row(
- buffer_row,
- &main_buffer.read(cx).snapshot(),
- ),
- )
- })
- .unwrap_or_default(),
- ),
- Some(DiffHunkStatus {
- kind: DiffHunkStatusKind::Added,
- ..
- }) => None,
- Some(DiffHunkStatus {
- kind: DiffHunkStatusKind::Deleted,
- ..
- }) => Some(BaseTextRow(buffer_row.unwrap())),
- Some(DiffHunkStatus {
- kind: DiffHunkStatusKind::Modified,
- ..
- }) => unreachable!(),
- };
let is_excerpt_start = region_ix == 0
|| ®ions[region_ix - 1].excerpt_id != ®ion.excerpt_id
|| regions[region_ix - 1].range.is_empty();
@@ -2589,7 +2556,6 @@ impl ReferenceMultibuffer {
buffer_id: region.buffer_id,
diff_status: region.status,
buffer_row,
- base_text_row,
wrapped_buffer_row: None,
multibuffer_row: Some(multibuffer_row),
@@ -3817,70 +3783,6 @@ async fn test_basic_filtering(cx: &mut TestAppContext) {
);
}
-#[gpui::test]
-async fn test_base_text_line_numbers(cx: &mut TestAppContext) {
- let base_text = indoc! {"
- one
- two
- three
- four
- five
- six
- "};
- let buffer_text = indoc! {"
- two
- THREE
- five
- six
- SEVEN
- "};
- let multibuffer = cx.update(|cx| MultiBuffer::build_simple(buffer_text, cx));
- multibuffer.update(cx, |multibuffer, cx| {
- let buffer = multibuffer.all_buffers().into_iter().next().unwrap();
- let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
- multibuffer.set_all_diff_hunks_expanded(cx);
- multibuffer.add_diff(diff, cx);
- });
- let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
- (multibuffer.snapshot(cx), multibuffer.subscribe())
- });
-
- assert_new_snapshot(
- &multibuffer,
- &mut snapshot,
- &mut subscription,
- cx,
- indoc! {"
- - one
- two
- - three
- - four
- + THREE
- five
- six
- + SEVEN
- "},
- );
- let base_text_rows = snapshot
- .row_infos(MultiBufferRow(0))
- .map(|row_info| row_info.base_text_row)
- .collect::<Vec<_>>();
- pretty_assertions::assert_eq!(
- base_text_rows,
- vec![
- Some(BaseTextRow(0)),
- Some(BaseTextRow(1)),
- Some(BaseTextRow(2)),
- Some(BaseTextRow(3)),
- None,
- Some(BaseTextRow(4)),
- Some(BaseTextRow(5)),
- None,
- Some(BaseTextRow(6)),
- ]
- )
-}
-
#[track_caller]
fn assert_excerpts_match(
multibuffer: &Entity<MultiBuffer>,
@@ -50,11 +50,6 @@ impl MultiBuffer {
if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
self.remove_excerpts(to_remove, cx)
}
- if let Some(follower) = &self.follower {
- follower.update(cx, |follower, cx| {
- follower.remove_excerpts_for_path(path, cx);
- });
- }
}
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
@@ -68,25 +63,11 @@ impl MultiBuffer {
self.excerpts_by_path.keys()
}
-
- // need:
- // MultiBuffer::add_inverted_diff
- //
- // SplittableEditor will handle:
- // - creating diff base buffers
- // - calling add_diff on one side and add_inverted_diff on the other side
- // - calling set_excerpts_for_path on both sides (using the diff base buffers for the LHS)
- // - and translating excerpt ranges for the LHS
- // - we have to make very sure that at all times, the sequence of excerpts on the two sides is the same
-
- // let b = Buffer::new(text);
- // let mb = MutliBuffer::new([b1, b2, b3], ...);
- // mb.attach_diff_hunks(...);
- // let mb2 = mb.inverted();
- //
- // fn inverted(&self) -> Self {
- //
- // }
+ pub fn excerpts_with_paths(&self) -> impl Iterator<Item = (&PathKey, &ExcerptId)> {
+ self.excerpts_by_path
+ .iter()
+ .flat_map(|(key, ex_ids)| ex_ids.iter().map(move |id| (key, id)))
+ }
/// Sets excerpts, returns `true` if at least one new excerpt was added.
pub fn set_excerpts_for_path(
@@ -173,7 +154,6 @@ impl MultiBuffer {
}
}
- // FIXME need to sync excerpt removal to the follower
pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
self.remove_excerpts(
self.excerpts_for_buffer(buffer, cx)
@@ -294,15 +274,7 @@ impl MultiBuffer {
(result, added_a_new_excerpt)
}
- // SplittableEditor (SplitEditor | Editor)
- // - lhs: Editor
- // - mb: MultiBuffer
- // - rhs: Editor
- // - mb: MultiBuffer
- //
- // editor.rhs.mb.follower = Some(editor.lhs.mb)
- // editor.lhs.mb.has_inverted_diffs = true
- fn update_path_excerpts(
+ pub fn update_path_excerpts(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
@@ -459,46 +431,6 @@ impl MultiBuffer {
self.excerpts_by_path.insert(path.clone(), excerpt_ids);
}
- if let Some(follower) = &self.follower {
- if let Some(diff) = snapshot.diffs.get(&buffer_snapshot.remote_id()) {
- follower.update(cx, |follower, cx| {
- let Some(base_text_buffer) = follower
- .base_text_buffers_by_main_buffer_id
- .get(&buffer_snapshot.remote_id())
- .cloned()
- else {
- return;
- };
- let new = new
- .into_iter()
- .map(|range| {
- let point_to_base_text_point = |point: Point| {
- let row = diff.row_to_base_text_row(point.row, buffer_snapshot);
- let column = diff.base_text().line_len(row);
- Point::new(row, column)
- };
- ExcerptRange {
- primary: point_to_base_text_point(range.primary.start)
- ..point_to_base_text_point(range.primary.end),
- context: point_to_base_text_point(range.context.start)
- ..point_to_base_text_point(range.context.end),
- }
- })
- .collect();
- let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();
- follower.update_path_excerpts(
- path,
- base_text_buffer,
- &base_text_buffer_snapshot,
- new,
- cx,
- );
- });
- } else {
- // FIXME
- }
- }
-
(excerpt_ids, added_a_new_excerpt)
}
}
@@ -7571,6 +7571,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
let event = diff_events.next().await.unwrap();
if let BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
+ base_text_changed_range: _,
} = event
{
let changed_range = changed_range.to_point(&snapshot);
@@ -7613,6 +7614,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
let event = diff_events.next().await.unwrap();
if let BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
+ base_text_changed_range: _,
} = event
{
let changed_range = changed_range.to_point(&snapshot);
@@ -7669,6 +7671,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
let event = diff_events.next().await.unwrap();
if let BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
+ base_text_changed_range: _,
} = event
{
let changed_range = changed_range.to_point(&snapshot);
@@ -7710,6 +7713,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
let event = diff_events.next().await.unwrap();
if let BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
+ base_text_changed_range: _,
} = event
{
let changed_range = changed_range.to_point(&snapshot);