diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 20ef9391888e6a824b87fe5de2607500049904ff..30e0e652195af38dbe25f0b7b71c5c5fff1c7179 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -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, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 61d316e3915a740cb35b24a3afa445a34a608336..842cc4b78d9cbc0368bf50a341e7f889d659ae7a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 89f9a6793d81e3de9ba27c97091fe446061c31ff..a4ddc086031050f49481b3e772667e1d071ce30c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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 }; diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 8a413a376f2296acdacddc97707a6112e8cd5185..98ce8bf8b56222990379e3a33be4902e5a384acd 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -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, 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, + (PathKey, Vec>), + > = 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, + ranges: impl IntoIterator>, + context_line_count: u32, + cx: &mut Context, + ) -> (Vec>, 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 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, + ) + }) + }); + } +} diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 56a54b64fdda158c971a21a36ba2a97ce3fce896..f8e2a76f6704459ea998587f2144900eaf4e8eb6 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -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>, - follower: Option>, - base_text_buffers_by_main_buffer_id: HashMap>, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -141,6 +139,8 @@ pub struct MultiBufferDiffHunk { pub diff_base_byte_range: Range, /// 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>, } impl MultiBufferDiffHunk { @@ -508,17 +508,31 @@ struct BufferState { struct DiffState { diff: Entity, + /// 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, - base_text_buffer: Entity, + base_text_buffer_id: BufferId, cx: &mut Context, ) -> 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>, - diffs: &'a TreeMap, + diffs: &'a TreeMap, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, buffer_chunk: Option>, range: Range, @@ -995,7 +1005,7 @@ impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for Diff struct MultiBufferCursor<'a, MBD, BD> { excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension>, diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms>, - diffs: &'a TreeMap, + diffs: &'a TreeMap, cached_region: Option>, } @@ -2252,10 +2262,30 @@ impl MultiBuffer { fn buffer_diff_language_changed(&mut self, diff: Entity, cx: &mut Context) { 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, + cx: &mut Context, + ) { + 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, @@ -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, diff_change_range: Range, - base_text_buffer: Entity, + base_text_buffer_id: BufferId, cx: &mut Context, ) { - 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> { + self.buffers.values().map(|state| state.buffer.clone()) + } + pub fn all_buffers(&self) -> HashSet> { - self.buffers - .values() - .map(|state| state.buffer.clone()) - .collect() + self.all_buffers_iter().collect() + } + + pub fn all_buffer_ids_iter(&self) -> impl Iterator { + self.buffers.keys().copied() } pub fn all_buffer_ids(&self) -> Vec { - self.buffers.keys().copied().collect() + self.all_buffer_ids_iter().collect() } pub fn buffer(&self, buffer_id: BufferId) -> Option> { @@ -2560,6 +2598,8 @@ impl MultiBuffer { } pub fn add_diff(&mut self, diff: Entity, cx: &mut Context) { + 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 = 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, + cx: &mut Context, + ) { + 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> { @@ -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> = + 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(&self, position: T) -> Option { 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> { + 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)] diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index fc2edcac15be72c60309c5c386393ad83c387860..1a84d1d0846967969e7170d5de4abddad7227b4e 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -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::>(); - 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, diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 79092d06a00a5720959f22289a8e2013a6e12d89..ac7aeba6b17cf43554963945e16f9aac1a49a783 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -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 { @@ -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 { + 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.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, @@ -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) } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 3117c0f5944d05a08524608a82587226a735550e..6ed04c6e25a4ab6bc6db5993bad10bc7f3c2ba2a 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -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);