Restructure git diff state management to allow viewing buffers with different diff bases (#21258)

This is a pure refactor of our Git diff state management. Buffers are no
longer are associated with one single diff (the unstaged changes).
Instead, there is an explicit project API for retrieving a buffer's
unstaged changes, and the `Editor` view layer is responsible for
choosing what diff to associate with a buffer.

The reason for this change is that we'll soon want to add multiple "git
diff views" to Zed, one of which will show the *uncommitted* changes for
a buffer. But that view will need to co-exist with other views of the
same buffer, which may want to show the unstaged changes.

### Todo

* [x] Get git gutter and git hunks working with new structure
* [x] Update editor tests to use new APIs
* [x] Update buffer tests
* [x] Restructure remoting/collab protocol
* [x] Update assertions about staged text in
`random_project_collaboration_tests`
* [x] Move buffer tests for git diff management to a new spot, using the
new APIs

Release Notes:

- N/A

---------

Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Cole <cole@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Max Brunsfeld
2024-12-04 15:02:33 -08:00
committed by GitHub
co-authored by Richard Cole Conrad
parent 31796171de
commit a2115e7242
29 changed files with 1832 additions and 1651 deletions
+301 -263
View File
@@ -1,12 +1,17 @@
use collections::{hash_map, HashMap, HashSet};
use collections::{HashMap, HashSet};
use git::diff::DiffHunkStatus;
use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Task, View};
use gpui::{
Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Subscription, Task,
View,
};
use language::{Buffer, BufferId, Point};
use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
MultiBufferSnapshot, ToPoint,
MultiBufferSnapshot, ToOffset, ToPoint,
};
use project::buffer_store::BufferChangeSet;
use std::{ops::Range, sync::Arc};
use sum_tree::TreeMap;
use text::OffsetRangeExt;
use ui::{
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
@@ -29,10 +34,11 @@ pub(super) struct HoveredHunk {
pub diff_base_byte_range: Range<usize>,
}
#[derive(Debug, Default)]
pub(super) struct ExpandedHunks {
#[derive(Default)]
pub(super) struct DiffMap {
pub(crate) hunks: Vec<ExpandedHunk>,
diff_base: HashMap<BufferId, DiffBaseBuffer>,
pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
pub(crate) snapshot: DiffMapSnapshot,
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
expand_all: bool,
}
@@ -46,10 +52,13 @@ pub(super) struct ExpandedHunk {
pub folded: bool,
}
#[derive(Debug)]
struct DiffBaseBuffer {
buffer: Model<Buffer>,
diff_base_version: usize,
#[derive(Clone, Debug, Default)]
pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
pub(crate) struct DiffBaseState {
pub(crate) change_set: Model<BufferChangeSet>,
pub(crate) last_version: Option<usize>,
_subscription: Subscription,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -66,7 +75,38 @@ pub enum DisplayDiffHunk {
},
}
impl ExpandedHunks {
impl DiffMap {
pub fn snapshot(&self) -> DiffMapSnapshot {
self.snapshot.clone()
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Editor>,
) {
let buffer_id = change_set.read(cx).buffer_id;
self.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
self.diff_bases.insert(
buffer_id,
DiffBaseState {
last_version: None,
_subscription: cx.observe(&change_set, move |editor, change_set, cx| {
editor
.diff_map
.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
}),
change_set,
},
);
}
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
self.hunks
.iter()
@@ -74,9 +114,92 @@ impl ExpandedHunks {
}
}
impl DiffMapSnapshot {
pub fn is_empty(&self) -> bool {
self.0.values().all(|diff| diff.is_empty())
}
pub fn diff_hunks<'a>(
&'a self,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
}
pub fn diff_hunks_in_range<'a, T: ToOffset>(
&'a self,
range: Range<T>,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
buffer_snapshot
.excerpts_for_range(range.clone())
.filter_map(move |excerpt| {
let buffer = excerpt.buffer();
let buffer_id = buffer.remote_id();
let diff = self.0.get(&buffer_id)?;
let buffer_range = excerpt.map_range_to_buffer(range.clone());
let buffer_range =
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
Some(
diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
.map(move |hunk| {
let start =
excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
let end =
excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
MultiBufferDiffHunk {
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
buffer_id,
buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
}
}),
)
})
.flatten()
}
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
&'a self,
range: Range<T>,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
buffer_snapshot
.excerpts_for_range_rev(range.clone())
.filter_map(move |excerpt| {
let buffer = excerpt.buffer();
let buffer_id = buffer.remote_id();
let diff = self.0.get(&buffer_id)?;
let buffer_range = excerpt.map_range_to_buffer(range.clone());
let buffer_range =
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
Some(
diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
.map(move |hunk| {
let start_row = excerpt
.map_point_from_buffer(Point::new(hunk.row_range.start, 0))
.row;
let end_row = excerpt
.map_point_from_buffer(Point::new(hunk.row_range.end, 0))
.row;
MultiBufferDiffHunk {
row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
buffer_id,
buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
}
}),
)
})
.flatten()
}
}
impl Editor {
pub fn set_expand_all_diff_hunks(&mut self) {
self.expanded_hunks.expand_all = true;
self.diff_map.expand_all = true;
}
pub(super) fn toggle_hovered_hunk(
@@ -92,18 +215,15 @@ impl Editor {
}
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let selections = self.selections.disjoint_anchors();
self.toggle_hunks_expanded(
hunks_for_selections(&multi_buffer_snapshot, &selections),
cx,
);
let snapshot = self.snapshot(cx);
let selections = self.selections.all(cx);
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
}
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let display_rows_with_expanded_hunks = self
.expanded_hunks
.diff_map
.hunks(false)
.map(|hunk| &hunk.hunk_range)
.map(|anchor_range| {
@@ -119,10 +239,10 @@ impl Editor {
)
})
.collect::<HashMap<_, _>>();
let hunks = snapshot
.display_snapshot
.buffer_snapshot
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
let hunks = self
.diff_map
.snapshot
.diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
.filter(|hunk| {
let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
.to_display_point(&snapshot.display_snapshot)
@@ -140,11 +260,11 @@ impl Editor {
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
cx: &mut ViewContext<Self>,
) {
if self.expanded_hunks.expand_all {
if self.diff_map.expand_all {
return;
}
let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
let previous_toggle_task = self.diff_map.hunk_update_tasks.remove(&None);
let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
if let Some(task) = previous_toggle_task {
task.await;
@@ -154,11 +274,10 @@ impl Editor {
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();
let mut highlights_to_remove =
Vec::with_capacity(editor.expanded_hunks.hunks.len());
let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
let mut blocks_to_remove = HashSet::default();
let mut hunks_to_expand = Vec::new();
editor.expanded_hunks.hunks.retain(|expanded_hunk| {
editor.diff_map.hunks.retain(|expanded_hunk| {
if expanded_hunk.folded {
return true;
}
@@ -238,7 +357,7 @@ impl Editor {
.ok();
});
self.expanded_hunks
self.diff_map
.hunk_update_tasks
.insert(None, cx.background_executor().spawn(new_toggle_task));
}
@@ -252,30 +371,34 @@ impl Editor {
let buffer = self.buffer.clone();
let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
let hunk_range = hunk.multi_buffer_range.clone();
let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
let buffer = buffer.buffer(hunk_range.start.buffer_id?)?;
let diff_base_buffer = diff_base_buffer
.or_else(|| self.current_diff_base_buffer(&buffer, cx))
.or_else(|| create_diff_base_buffer(&buffer, cx))?;
let deleted_text_lines = buffer.read(cx).diff_base().map(|diff_base| {
let diff_start_row = diff_base
.offset_to_point(hunk.diff_base_byte_range.start)
.row;
let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
diff_end_row - diff_start_row
})?;
Some((diff_base_buffer, deleted_text_lines))
let buffer_id = hunk_range.start.buffer_id?;
let diff_base_buffer = diff_base_buffer.or_else(|| {
self.diff_map
.diff_bases
.get(&buffer_id)?
.change_set
.read(cx)
.base_text
.clone()
})?;
let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {
probe
.hunk_range
.start
.cmp(&hunk_range.start, &multi_buffer_snapshot)
}) {
Ok(_already_present) => return None,
Err(ix) => ix,
};
let diff_base = diff_base_buffer.read(cx);
let diff_start_row = diff_base
.offset_to_point(hunk.diff_base_byte_range.start)
.row;
let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
let deleted_text_lines = diff_end_row - diff_start_row;
let block_insert_index = self
.diff_map
.hunks
.binary_search_by(|probe| {
probe
.hunk_range
.start
.cmp(&hunk_range.start, &multi_buffer_snapshot)
})
.err()?;
let blocks;
match hunk.status {
@@ -315,7 +438,7 @@ impl Editor {
);
}
};
self.expanded_hunks.hunks.insert(
self.diff_map.hunks.insert(
block_insert_index,
ExpandedHunk {
blocks,
@@ -374,8 +497,8 @@ impl Editor {
_: &ApplyDiffHunk,
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
let snapshot = self.snapshot(cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
let mut ranges_by_buffer = HashMap::default();
self.transact(cx, |editor, cx| {
for hunk in hunks {
@@ -401,7 +524,7 @@ impl Editor {
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);
let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
hunks.nth(1).is_some()
}
@@ -415,7 +538,7 @@ impl Editor {
.read(cx)
.point_to_buffer_offset(hunk.multi_buffer_range.start, cx)
.map_or(false, |(buffer, _, _)| {
buffer.read(cx).diff_base_buffer().is_some()
buffer.read(cx).base_buffer().is_some()
});
let border_color = cx.theme().colors().border_variant;
@@ -552,29 +675,9 @@ impl Editor {
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
let multi_buffer =
editor.read(cx).buffer().clone();
let multi_buffer_snapshot =
multi_buffer.read(cx).snapshot(cx);
let mut revert_changes = HashMap::default();
if let Some(hunk) =
crate::hunk_diff::to_diff_hunk(
&hunk,
&multi_buffer_snapshot,
)
{
Editor::prepare_revert_change(
&mut revert_changes,
&multi_buffer,
&hunk,
cx,
);
}
if !revert_changes.is_empty() {
editor.update(cx, |editor, cx| {
editor.revert(revert_changes, cx)
});
}
editor.update(cx, |editor, cx| {
editor.revert_hunk(hunk.clone(), cx);
});
}
}),
)
@@ -763,13 +866,13 @@ impl Editor {
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
if self.expanded_hunks.expand_all {
if self.diff_map.expand_all {
return false;
}
self.expanded_hunks.hunk_update_tasks.clear();
self.diff_map.hunk_update_tasks.clear();
self.clear_row_highlights::<DiffRowHighlight>();
let to_remove = self
.expanded_hunks
.diff_map
.hunks
.drain(..)
.flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
@@ -783,48 +886,39 @@ impl Editor {
}
pub(super) fn sync_expanded_diff_hunks(
&mut self,
buffer: Model<Buffer>,
diff_map: &mut DiffMap,
buffer_id: BufferId,
cx: &mut ViewContext<'_, Self>,
) {
let buffer_id = buffer.read(cx).remote_id();
let buffer_diff_base_version = buffer.read(cx).diff_base_version();
self.expanded_hunks
.hunk_update_tasks
.remove(&Some(buffer_id));
let diff_base_buffer = self.current_diff_base_buffer(&buffer, cx);
let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
let mut diff_base_buffer = None;
let mut diff_base_buffer_unchanged = true;
if let Some(diff_base_state) = diff_base_state {
diff_base_state.change_set.update(cx, |change_set, _| {
if diff_base_state.last_version != Some(change_set.base_text_version) {
diff_base_state.last_version = Some(change_set.base_text_version);
diff_base_buffer_unchanged = false;
}
diff_base_buffer = change_set.base_text.clone();
})
}
diff_map.hunk_update_tasks.remove(&Some(buffer_id));
let new_sync_task = cx.spawn(move |editor, mut cx| async move {
let diff_base_buffer_unchanged = diff_base_buffer.is_some();
let Ok(diff_base_buffer) =
cx.update(|cx| diff_base_buffer.or_else(|| create_diff_base_buffer(&buffer, cx)))
else {
return;
};
editor
.update(&mut cx, |editor, cx| {
if let Some(diff_base_buffer) = &diff_base_buffer {
editor.expanded_hunks.diff_base.insert(
buffer_id,
DiffBaseBuffer {
buffer: diff_base_buffer.clone(),
diff_base_version: buffer_diff_base_version,
},
);
}
let snapshot = editor.snapshot(cx);
let mut recalculated_hunks = snapshot
.buffer_snapshot
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
.diff_map
.diff_hunks(&snapshot.buffer_snapshot)
.filter(|hunk| hunk.buffer_id == buffer_id)
.fuse()
.peekable();
let mut highlights_to_remove =
Vec::with_capacity(editor.expanded_hunks.hunks.len());
let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
let mut blocks_to_remove = HashSet::default();
let mut hunks_to_reexpand =
Vec::with_capacity(editor.expanded_hunks.hunks.len());
editor.expanded_hunks.hunks.retain_mut(|expanded_hunk| {
let mut hunks_to_reexpand = Vec::with_capacity(editor.diff_map.hunks.len());
editor.diff_map.hunks.retain_mut(|expanded_hunk| {
if expanded_hunk.hunk_range.start.buffer_id != Some(buffer_id) {
return true;
};
@@ -874,7 +968,7 @@ impl Editor {
> hunk_display_range.end
{
recalculated_hunks.next();
if editor.expanded_hunks.expand_all {
if editor.diff_map.expand_all {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
@@ -917,7 +1011,7 @@ impl Editor {
retain
});
if editor.expanded_hunks.expand_all {
if editor.diff_map.expand_all {
for hunk in recalculated_hunks {
match diff_hunk_to_display(&hunk, &snapshot) {
DisplayDiffHunk::Folded { .. } => {}
@@ -935,6 +1029,8 @@ impl Editor {
}
}
}
} else {
drop(recalculated_hunks);
}
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
@@ -949,32 +1045,12 @@ impl Editor {
.ok();
});
self.expanded_hunks.hunk_update_tasks.insert(
diff_map.hunk_update_tasks.insert(
Some(buffer_id),
cx.background_executor().spawn(new_sync_task),
);
}
fn current_diff_base_buffer(
&mut self,
buffer: &Model<Buffer>,
cx: &mut AppContext,
) -> Option<Model<Buffer>> {
buffer.update(cx, |buffer, _| {
match self.expanded_hunks.diff_base.entry(buffer.remote_id()) {
hash_map::Entry::Occupied(o) => {
if o.get().diff_base_version != buffer.diff_base_version() {
o.remove();
None
} else {
Some(o.get().buffer.clone())
}
}
hash_map::Entry::Vacant(_) => None,
}
})
}
fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let position = position.to_point(&snapshot.buffer_snapshot);
@@ -1021,7 +1097,7 @@ impl Editor {
}
}
fn to_diff_hunk(
pub(crate) fn to_diff_hunk(
hovered_hunk: &HoveredHunk,
multi_buffer_snapshot: &MultiBufferSnapshot,
) -> Option<MultiBufferDiffHunk> {
@@ -1043,24 +1119,6 @@ fn to_diff_hunk(
})
}
fn create_diff_base_buffer(buffer: &Model<Buffer>, cx: &mut AppContext) -> Option<Model<Buffer>> {
buffer
.update(cx, |buffer, _| {
let language = buffer.language().cloned();
let diff_base = buffer.diff_base()?.clone();
Some((buffer.line_ending(), diff_base, language))
})
.map(|(line_ending, diff_base, language)| {
cx.new_model(|cx| {
let buffer = Buffer::local_normalized(diff_base, line_ending, cx);
match language {
Some(language) => buffer.with_language(language, cx),
None => buffer,
}
})
})
}
fn added_hunk_color(cx: &AppContext) -> Hsla {
let mut created_color = cx.theme().status().git().created;
created_color.fade_out(0.7);
@@ -1118,51 +1176,27 @@ fn editor_with_deleted_text(
});
})]);
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
let diff_base_range = hunk.diff_base_byte_range.clone();
editor
.register_action::<RevertSelectedHunks>({
let hunk = hunk.clone();
let parent_editor = parent_editor.clone();
move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
let Some((buffer, original_text)) =
editor.buffer().update(cx, |buffer, cx| {
let (_, buffer, _) = buffer.excerpt_containing(
original_multi_buffer_range.start,
cx,
)?;
let original_text =
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
Some((buffer, Arc::from(original_text.to_string())))
})
else {
return;
};
buffer.update(cx, |buffer, cx| {
buffer.edit(
Some((
original_multi_buffer_range.start.text_anchor
..original_multi_buffer_range.end.text_anchor,
original_text,
)),
None,
cx,
)
});
})
.update(cx, |editor, cx| editor.revert_hunk(hunk.clone(), cx))
.ok();
}
})
.detach();
let hunk = hunk.clone();
editor
.register_action::<ToggleHunkDiff>(move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
})
.ok();
.register_action::<ToggleHunkDiff>({
let hunk = hunk.clone();
move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
})
.ok();
}
})
.detach();
editor
@@ -1272,78 +1306,57 @@ mod tests {
let project = Project::test(fs, [], cx).await;
// buffer has two modified hunks with two rows each
let buffer_1 = project.update(cx, |project, cx| {
project.create_local_buffer(
"
1.zero
1.ONE
1.TWO
1.three
1.FOUR
1.FIVE
1.six
"
.unindent()
.as_str(),
None,
cx,
)
});
buffer_1.update(cx, |buffer, cx| {
buffer.set_diff_base(
Some(
"
1.zero
1.one
1.two
1.three
1.four
1.five
1.six
"
.unindent(),
),
cx,
);
});
let diff_base_1 = "
1.zero
1.one
1.two
1.three
1.four
1.five
1.six
"
.unindent();
let text_1 = "
1.zero
1.ONE
1.TWO
1.three
1.FOUR
1.FIVE
1.six
"
.unindent();
// buffer has a deletion hunk and an insertion hunk
let buffer_2 = project.update(cx, |project, cx| {
project.create_local_buffer(
"
2.zero
2.one
2.two
2.three
2.four
2.five
2.six
"
.unindent()
.as_str(),
None,
cx,
)
});
buffer_2.update(cx, |buffer, cx| {
buffer.set_diff_base(
Some(
"
2.zero
2.one
2.one-and-a-half
2.two
2.three
2.four
2.six
"
.unindent(),
),
cx,
);
});
let diff_base_2 = "
2.zero
2.one
2.one-and-a-half
2.two
2.three
2.four
2.six
"
.unindent();
cx.background_executor.run_until_parked();
let text_2 = "
2.zero
2.one
2.two
2.three
2.four
2.five
2.six
"
.unindent();
let buffer_1 = project.update(cx, |project, cx| {
project.create_local_buffer(text_1.as_str(), None, cx)
});
let buffer_2 = project.update(cx, |project, cx| {
project.create_local_buffer(text_2.as_str(), None, cx)
});
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(ReadWrite);
@@ -1392,10 +1405,30 @@ mod tests {
multibuffer
});
let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, false, cx));
editor
.update(cx, |editor, cx| {
for (buffer, diff_base) in [
(buffer_1.clone(), diff_base_1),
(buffer_2.clone(), diff_base_2),
] {
let change_set = cx.new_model(|cx| {
BufferChangeSet::new_with_base_text(
diff_base.to_string(),
buffer.read(cx).text_snapshot(),
cx,
)
});
editor.diff_map.add_change_set(change_set, cx)
}
})
.unwrap();
cx.background_executor.run_until_parked();
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
assert_eq!(
snapshot.text(),
snapshot.buffer_snapshot.text(),
"
1.zero
1.ONE
@@ -1438,7 +1471,8 @@ mod tests {
assert_eq!(
snapshot
.git_diff_hunks_in_range(MultiBufferRow(0)..MultiBufferRow(12))
.diff_map
.diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
.collect::<Vec<_>>(),
&expected,
@@ -1446,7 +1480,11 @@ mod tests {
assert_eq!(
snapshot
.git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
.diff_map
.diff_hunks_in_range_rev(
Point::zero()..Point::new(12, 0),
&snapshot.buffer_snapshot
)
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
.collect::<Vec<_>>(),
expected