diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 0fe240d04a1e6f6bf52a74b77592123914962641..cd9daa5fa268f9a7cb380b58640452ae10dd8b00 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -1400,7 +1400,7 @@ impl BufferDiff { } #[cfg(any(test, feature = "test-support"))] - pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context) { + pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context) { let language = self.base_text(cx).language().cloned(); let base_text = self.base_text_string(cx).map(|s| s.as_str().into()); let fut = self.update_diff(buffer.clone(), base_text, false, language, cx); diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 7fd7262503dca8ef35a3882bb82297ebae2f60a1..ec313e1f17b08b62ccd59171c5e0e26f06f82b12 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -39,6 +39,7 @@ struct SplitDiff; struct UnsplitDiff; pub struct SplittableEditor { + primary_multibuffer: Entity, primary_editor: Entity, secondary: Option, panes: PaneGroup, @@ -47,6 +48,7 @@ pub struct SplittableEditor { } struct SecondaryEditor { + multibuffer: Entity, editor: Entity, pane: Entity, has_latest_selection: bool, @@ -69,14 +71,20 @@ impl SplittableEditor { } pub fn new_unsplit( - buffer: Entity, + primary_multibuffer: Entity, project: Entity, workspace: Entity, window: &mut Window, cx: &mut Context, ) -> Self { - let primary_editor = - cx.new(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), window, cx)); + let primary_editor = cx.new(|cx| { + Editor::for_multibuffer( + primary_multibuffer.clone(), + Some(project.clone()), + window, + cx, + ) + }); let pane = cx.new(|cx| { let mut pane = Pane::new( workspace.downgrade(), @@ -121,6 +129,7 @@ impl SplittableEditor { }); Self { primary_editor, + primary_multibuffer, secondary: None, panes, workspace: workspace.downgrade(), @@ -140,14 +149,18 @@ impl SplittableEditor { }; let project = workspace.read(cx).project().clone(); + let secondary_multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(Capability::ReadOnly); + multibuffer.set_all_diff_hunks_expanded(cx); + multibuffer + }); let secondary_editor = cx.new(|cx| { - let multibuffer = cx.new(|cx| { - let mut multibuffer = MultiBuffer::new(Capability::ReadOnly); - multibuffer.set_all_diff_hunks_expanded(cx); - multibuffer - }); - let mut editor = - Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx); + let mut editor = Editor::for_multibuffer( + secondary_multibuffer.clone(), + Some(project.clone()), + window, + cx, + ); editor.number_deleted_lines = true; editor }); @@ -187,6 +200,7 @@ impl SplittableEditor { ]; let mut secondary = SecondaryEditor { editor: secondary_editor, + multibuffer: secondary_multibuffer, pane: secondary_pane.clone(), has_latest_selection: false, _subscriptions: subscriptions, @@ -273,61 +287,104 @@ impl SplittableEditor { }) } - fn expand_primary_excerpts( + /// Expands excerpts in both sides. + /// + /// While the left multibuffer does have separate excerpts with separate + /// IDs, this is an implementation detail. We do not expose the left excerpt + /// IDs in the public API of [`SplittableEditor`]. + pub fn expand_excerpts( &mut self, excerpt_ids: impl Iterator + Clone, lines: u32, direction: ExpandExcerptDirection, cx: &mut Context, ) { - self.update_primary_multibuffer(cx, |multibuffer, cx| { + self.primary_multibuffer.update(cx, |multibuffer, cx| { multibuffer.expand_excerpts(excerpt_ids.clone(), lines, direction, cx); }); - let paths: Vec = excerpt_ids + let paths: Vec<(ExcerptId, PathKey)> = excerpt_ids .flat_map(|excerpt_id| { - self.primary_multibuffer(cx) + let path = self + .primary_multibuffer + .read(cx) .path_for_excerpt(excerpt_id) - .cloned() + .cloned()?; + Some((excerpt_id, path)) }) .collect(); - if let Some(secondary) = &self.secondary { - self.update_primary_multibuffer(cx, |multibuffer, cx| { - let snapshot = primary_multibuffer.snapshot(cx); - for path in paths { - let buffer = snapshot.buffer_for_excerpt(excerpt_id).unwrap(); - let diff = primary_multibuffer.diff_for(buffer.remote_id()).unwrap(); - secondary.sync_path_excerpts(path, multibuffer, diff, cx); - } + if let Some(secondary) = &mut self.secondary { + self.primary_editor.update(cx, |editor, cx| { + editor.buffer().update(cx, |multibuffer, cx| { + let snapshot = multibuffer.snapshot(cx); + for (excerpt_id, path) in paths { + let buffer = snapshot.buffer_for_excerpt(excerpt_id).unwrap(); + let diff = multibuffer.diff_for(buffer.remote_id()).unwrap(); + secondary.sync_path_excerpts(path, multibuffer, diff, cx); + } + }) }) } } - fn primary_multibuffer<'a>(&'a self, cx: &'a Context) -> &'a MultiBuffer { - self.primary_editor.read(cx).buffer.read(cx) - } - - fn update_primary_multibuffer( - &mut self, - cx: &mut Context, - f: impl FnOnce(&mut MultiBuffer, &mut Context) -> R, - ) -> R { - self.primary_editor - .update(cx, |editor, cx| editor.buffer().update(cx, f)) + pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context) { + self.primary_multibuffer.update(cx, |buffer, cx| { + buffer.remove_excerpts_for_path(path.clone(), cx) + }); + if let Some(secondary) = &self.secondary { + secondary + .multibuffer + .update(cx, |buffer, cx| buffer.remove_excerpts_for_path(path, cx)) + } } +} - #[cfg(test)] +#[cfg(test)] +impl SplittableEditor { fn check_invariants(&self, cx: &App) { - todo!() + let Some(secondary) = &self.secondary else { + return; + }; + + let primary_excerpts = self.primary_multibuffer.read(cx).excerpt_ids(); + let secondary_excerpts = secondary.multibuffer.read(cx).excerpt_ids(); + assert_eq!(primary_excerpts.len(), secondary_excerpts.len()); + + // self.primary_multibuffer.read(cx).check_invariants(cx); + // secondary.multibuffer.read(cx).check_invariants(cx); + // Assertions:... + // + // left.display_lines().filter(is_unmodified) == right.display_lines().filter(is_unmodified) + // + // left excerpts and right excerpts bijectivity + // + // + + // let primary_buffer_text = self + // .primary_multibuffer + // .read(cx) + // .text_summary_for_range(Anchor::min()..Anchor::max()); + // let secondary_buffer_text = secondary + // .multibuffer + // .read(cx) + // .text_summary_for_range(Anchor::min()..Anchor::max()); + // let primary_buffer_base_text = self + // .primary_multibuffer + // .read(cx) + // .base_text_summary_for_range(Anchor::min()..Anchor::max()); + // let secondary_buffer_base_text = secondary + // .multibuffer + // .read(cx) + // .base_text_summary_for_range(Anchor::min()..Anchor::max()); } - #[cfg(test)] fn randomly_edit_excerpts( &mut self, rng: &mut impl rand::Rng, mutation_count: usize, cx: &mut Context, ) { + use collections::HashSet; use rand::prelude::*; use std::env; use util::RandomCharIter; @@ -336,19 +393,21 @@ impl SplittableEditor { .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable")) .unwrap_or(5); - let excerpt_ids = self.primary_multibuffer(cx).excerpt_ids(); + let paths = self + .primary_multibuffer + .read(cx) + .paths() + .collect::>(); + let excerpt_ids = self.primary_multibuffer.read(cx).excerpt_ids(); - let mut buffers = Vec::new(); for _ in 0..mutation_count { if rng.random_bool(0.05) { log::info!("Clearing multi-buffer"); - self.update_primary_multibuffer(cx, |multibuffer, cx| { + self.primary_multibuffer.update(cx, |multibuffer, cx| { multibuffer.clear(cx); }); continue; } else if rng.random_bool(0.1) && !excerpt_ids.is_empty() { - use collections::HashSet; - let mut excerpts = HashSet::default(); for _ in 0..rng.random_range(0..excerpt_ids.len()) { excerpts.extend(excerpt_ids.choose(rng).copied()); @@ -358,8 +417,7 @@ impl SplittableEditor { log::info!("Expanding excerpts {excerpts:?} by {line_count} lines"); - // FIXME we need an expand_excerpts API on the splittable editor that does a sync - self.expand_primary_excerpts( + self.expand_excerpts( excerpts.iter().cloned(), line_count, ExpandExcerptDirection::UpAndDown, @@ -369,58 +427,50 @@ impl SplittableEditor { } if excerpt_ids.is_empty() || (rng.random() && excerpt_ids.len() < max_excerpts) { - let buffer_handle = if rng.random() || self.buffers.is_empty() { - let text = RandomCharIter::new(&mut *rng).take(10).collect::(); - buffers.push(cx.new(|cx| Buffer::local(text, cx))); - let buffer = buffers.last().unwrap().read(cx); + let existing_buffers = self.primary_multibuffer.read(cx).all_buffers(); + let buffer = if rng.random() || existing_buffers.is_empty() { + let len = rng.random_range(0..500); + let text = RandomCharIter::new(&mut *rng).take(len).collect::(); + let buffer = cx.new(|cx| Buffer::local(text, cx)); log::info!( "Creating new buffer {} with text: {:?}", - buffer.remote_id(), - buffer.text() + buffer.read(cx).remote_id(), + buffer.read(cx).text() ); - buffers.last().unwrap().clone() + buffer } else { - self.buffers.values().choose(rng).unwrap().buffer.clone() + existing_buffers.iter().choose(rng).unwrap().clone() }; - let buffer = buffer_handle.read(cx); - let buffer_text = buffer.text(); - let ranges = (0..rng.random_range(0..5)) - .map(|_| { - let end_ix = - buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Right); - let start_ix = buffer.clip_offset(rng.random_range(0..=end_ix), Bias::Left); - ExcerptRange::new(start_ix..end_ix) - }) - .collect::>(); - log::info!( - "Inserting excerpts from buffer {} and ranges {:?}: {:?}", - buffer_handle.read(cx).remote_id(), - ranges.iter().map(|r| &r.context).collect::>(), - ranges - .iter() - .map(|r| &buffer_text[r.context.clone()]) + let buffer_snapshot = buffer.read(cx).snapshot(); + let diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_snapshot, cx)); + // Create some initial diff hunks. + buffer.update(cx, |buffer, cx| { + buffer.randomly_edit(rng, 2, cx); + }); + let buffer_snapshot = buffer.read(cx).text_snapshot(); + let ranges = diff.update(cx, |diff, cx| { + diff.recalculate_diff_sync(&buffer_snapshot, cx); + diff.snapshot(cx) + .hunks(&buffer_snapshot) + .map(|hunk| hunk.range.clone()) .collect::>() - ); - - let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx); - log::info!("Inserted with ids: {:?}", excerpt_id); + }); + let path = PathKey::for_buffer(&buffer, cx); + self.set_excerpts_for_path(path, buffer, ranges, 2, diff, cx); } else { - let remove_count = rng.random_range(1..=excerpt_ids.len()); - let mut excerpts_to_remove = excerpt_ids + let remove_count = rng.random_range(1..=paths.len()); + let paths_to_remove = paths .choose_multiple(rng, remove_count) .cloned() .collect::>(); - let snapshot = self.snapshot.borrow(); - excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot)); - drop(snapshot); - log::info!("Removing excerpts {:?}", excerpts_to_remove); - self.remove_excerpts(excerpts_to_remove, cx); + for path in paths_to_remove { + self.remove_excerpts_for_path(path, cx); + } } } } - #[cfg(test)] fn randomly_mutate( &mut self, rng: &mut impl rand::Rng, @@ -442,12 +492,31 @@ impl SplittableEditor { } }); } else { - self.update_primary_multibuffer(cx, |multibuffer, cx| { + self.primary_multibuffer.update(cx, |multibuffer, cx| { multibuffer.randomly_edit(rng, mutation_count, cx); }); } - } else { + } else if rng.random() { self.randomly_edit_excerpts(rng, mutation_count, cx); + } else { + for buffer in self.primary_multibuffer.read(cx).all_buffers() { + let diff = self + .primary_multibuffer + .read(cx) + .diff_for(buffer.read(cx).remote_id()) + .unwrap(); + let buffer_snapshot = buffer.read(cx).text_snapshot(); + diff.update(cx, |diff, cx| { + diff.recalculate_diff_sync(&buffer_snapshot, cx); + }); + // TODO(split-diff) might be a good idea to try to separate the diff recalculation from the excerpt recalculation + let diff_snapshot = diff.read(cx).snapshot(cx); + let ranges = diff_snapshot + .hunks(&buffer_snapshot) + .map(|hunk| hunk.range.clone()); + let path = PathKey::for_buffer(&buffer, cx); + self.set_excerpts_for_path(path, buffer, ranges, 2, diff, cx); + } } self.check_invariants(cx); @@ -516,16 +585,15 @@ impl SecondaryEditor { let point_range_to_base_text_point_range = |range: Range| { let start_row = diff_snapshot.row_to_base_text_row(range.start.row, main_buffer); - let start_column = 0; let end_row = diff_snapshot.row_to_base_text_row(range.end.row, main_buffer); let end_column = diff_snapshot.base_text().line_len(end_row); - Point::new(start_row, start_column)..Point::new(end_row, end_column) + Point::new(start_row, 0)..Point::new(end_row, end_column) }; let primary = excerpt_range.primary.to_point(main_buffer); let context = excerpt_range.context.to_point(main_buffer); ExcerptRange { - primary: point_range_to_base_text_point_range(primary), - context: point_range_to_base_text_point_range(context), + primary: point_range_to_base_text_point_range(dbg!(primary)), + context: point_range_to_base_text_point_range(dbg!(context)), } }) .collect(); @@ -556,21 +624,25 @@ mod tests { use language::{Buffer, Capability}; use multi_buffer::MultiBuffer; use project::Project; - use rand::rngs::StdRng; + use rand::{Rng, rngs::StdRng}; use settings::SettingsStore; use ui::VisualContext as _; use workspace::Workspace; use crate::SplittableEditor; - #[gpui::test] - async fn test_basic_excerpts(mut rng: StdRng, cx: &mut gpui::TestAppContext) { + fn init_test(cx: &mut gpui::TestAppContext) { cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); theme::init(theme::LoadThemes::JustBase, cx); crate::init(cx); }); + } + + #[gpui::test] + async fn test_basic_excerpts(mut rng: StdRng, cx: &mut gpui::TestAppContext) { + init_test(cx); let base_text = indoc! {" hello "}; @@ -589,9 +661,9 @@ mod tests { SplittableEditor::new_unsplit(multibuffer, project, workspace, window, cx) }); - let mutation_count = rng.gen_range(0..100); + let mutation_count = rng.random_range(0..100); editor.update(cx, |editor, cx| { - editor.randomly_mutate(rng, mutation_count, cx); + editor.randomly_mutate(&mut rng, mutation_count, cx); }) // for _ in 0..random() { @@ -605,208 +677,4 @@ mod tests { // editor.read(cx).primary_editor().read(cx).display_map.read(cx) } - - // MultiB - - // 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 - - // "}); - // } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index d37d0b57cfc8baa5f89191a0c5e577658c1bd49f..976da812a6d52a2aabbf65156c160e70fbc42e72 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3708,7 +3708,7 @@ impl MultiBuffer { self.check_invariants(cx); } - pub fn check_invariants(&self, cx: &App) { + fn check_invariants(&self, cx: &App) { self.read(cx).check_invariants(); } } diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index f77c43249c33dd6adf7fda5ccaff54f538ff5cc8..e1c55ed8b5e33f7fd5fc0740792d9dc58efd4997 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -994,7 +994,7 @@ async fn test_empty_diff_excerpt(cx: &mut TestAppContext) { buffer.update(cx, |buffer, cx| { buffer.edit([(0..0, "a\nb\nc")], None, cx); diff.update(cx, |diff, cx| { - diff.recalculate_diff_sync(buffer.snapshot().text, cx); + diff.recalculate_diff_sync(&buffer.text_snapshot(), cx); }); assert_eq!(buffer.text(), "a\nb\nc") }); @@ -1006,7 +1006,7 @@ async fn test_empty_diff_excerpt(cx: &mut TestAppContext) { buffer.update(cx, |buffer, cx| { buffer.undo(cx); diff.update(cx, |diff, cx| { - diff.recalculate_diff_sync(buffer.snapshot().text, cx); + diff.recalculate_diff_sync(&buffer.text_snapshot(), cx); }); assert_eq!(buffer.text(), "") }); @@ -1507,7 +1507,7 @@ async fn test_basic_diff_hunks(cx: &mut TestAppContext) { // Recalculate the diff, changing the first diff hunk. diff.update(cx, |diff, cx| { - diff.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx); + diff.recalculate_diff_sync(&buffer.read(cx).text_snapshot(), cx); }); cx.run_until_parked(); assert_new_snapshot( @@ -2898,7 +2898,7 @@ async fn test_random_multibuffer_impl( "recalculating diff for buffer {:?}", snapshot.remote_id(), ); - diff.recalculate_diff_sync(snapshot.text, cx); + diff.recalculate_diff_sync(&snapshot.text, cx); }, ); } diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index c6f99912a6f05308836f63ab2182ca8524fcc7d8..429dbe2d572de76e5633d5fa8b39a2cc53f97bb0 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -46,6 +46,10 @@ impl MultiBuffer { self.excerpts_by_path.keys().cloned() } + pub fn path_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&PathKey> { + self.paths_by_excerpt.get(&excerpt_id) + } + pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context) { if let Some(to_remove) = self.excerpts_by_path.remove(&path) { self.remove_excerpts(to_remove, cx)