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
parent 31796171de
commit a2115e7242
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1832 additions and 1651 deletions

View file

@ -95,10 +95,7 @@ pub enum Event {
},
Reloaded,
ReloadNeeded,
DiffBaseChanged,
DiffUpdated {
buffer: Model<Buffer>,
},
LanguageChanged(BufferId),
CapabilityChanged,
Reparsed(BufferId),
@ -257,6 +254,7 @@ struct Excerpt {
pub struct MultiBufferExcerpt<'a> {
excerpt: &'a Excerpt,
excerpt_offset: usize,
excerpt_position: Point,
}
#[derive(Clone, Debug)]
@ -1824,8 +1822,6 @@ impl MultiBuffer {
language::BufferEvent::FileHandleChanged => Event::FileHandleChanged,
language::BufferEvent::Reloaded => Event::Reloaded,
language::BufferEvent::ReloadNeeded => Event::ReloadNeeded,
language::BufferEvent::DiffBaseChanged => Event::DiffBaseChanged,
language::BufferEvent::DiffUpdated => Event::DiffUpdated { buffer },
language::BufferEvent::LanguageChanged => {
Event::LanguageChanged(buffer.read(cx).remote_id())
}
@ -3424,47 +3420,86 @@ impl MultiBufferSnapshot {
.map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone()))
}
fn excerpts_for_range<T: ToOffset>(
pub fn all_excerpts(&self) -> impl Iterator<Item = MultiBufferExcerpt> {
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.next(&());
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
let excerpt = MultiBufferExcerpt::new(excerpt, *cursor.start());
cursor.next(&());
Some(excerpt)
})
}
pub fn excerpts_for_range<T: ToOffset>(
&self,
range: Range<T>,
) -> impl Iterator<Item = (&Excerpt, usize)> + '_ {
) -> impl Iterator<Item = MultiBufferExcerpt> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>(&());
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.seek(&range.start, Bias::Right, &());
cursor.prev(&());
iter::from_fn(move || {
cursor.next(&());
if cursor.start() < &range.end {
cursor.item().map(|item| (item, *cursor.start()))
if cursor.start().0 < range.end {
cursor
.item()
.map(|item| MultiBufferExcerpt::new(item, *cursor.start()))
} else {
None
}
})
}
pub fn excerpts_for_range_rev<T: ToOffset>(
&self,
range: Range<T>,
) -> impl Iterator<Item = MultiBufferExcerpt> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.seek(&range.end, Bias::Left, &());
if cursor.item().is_none() {
cursor.prev(&());
}
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
let excerpt = MultiBufferExcerpt::new(excerpt, *cursor.start());
cursor.prev(&());
Some(excerpt)
})
}
pub fn excerpt_before(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
let start_locator = self.excerpt_locator_for_id(id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(&Some(start_locator), Bias::Left, &());
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
cursor.seek(start_locator, Bias::Left, &());
cursor.prev(&());
let excerpt = cursor.item()?;
let excerpt_offset = cursor.start().text.len;
let excerpt_position = cursor.start().text.lines;
Some(MultiBufferExcerpt {
excerpt,
excerpt_offset: 0,
excerpt_offset,
excerpt_position,
})
}
pub fn excerpt_after(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
let start_locator = self.excerpt_locator_for_id(id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(&Some(start_locator), Bias::Left, &());
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
cursor.seek(start_locator, Bias::Left, &());
cursor.next(&());
let excerpt = cursor.item()?;
let excerpt_offset = cursor.start().text.len;
let excerpt_position = cursor.start().text.lines;
Some(MultiBufferExcerpt {
excerpt,
excerpt_offset: 0,
excerpt_offset,
excerpt_position,
})
}
@ -3647,22 +3682,12 @@ impl MultiBufferSnapshot {
) -> impl Iterator<Item = Range<usize>> + 'a {
let range = range.start.to_offset(self)..range.end.to_offset(self);
self.excerpts_for_range(range.clone())
.filter(move |&(excerpt, _)| redaction_enabled(excerpt.buffer.file()))
.flat_map(move |(excerpt, excerpt_offset)| {
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
.filter(move |excerpt| redaction_enabled(excerpt.buffer().file()))
.flat_map(move |excerpt| {
excerpt
.buffer
.redacted_ranges(excerpt.range.context.clone())
.map(move |mut redacted_range| {
// Re-base onto the excerpts coordinates in the multibuffer
redacted_range.start = excerpt_offset
+ redacted_range.start.saturating_sub(excerpt_buffer_start);
redacted_range.end = excerpt_offset
+ redacted_range.end.saturating_sub(excerpt_buffer_start);
redacted_range
})
.buffer()
.redacted_ranges(excerpt.buffer_range().clone())
.map(move |redacted_range| excerpt.map_range_from_buffer(redacted_range))
.skip_while(move |redacted_range| redacted_range.end < range.start)
.take_while(move |redacted_range| redacted_range.start < range.end)
})
@ -3674,12 +3699,13 @@ impl MultiBufferSnapshot {
) -> impl Iterator<Item = language::RunnableRange> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
self.excerpts_for_range(range.clone())
.flat_map(move |(excerpt, excerpt_offset)| {
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
.flat_map(move |excerpt| {
let excerpt_buffer_start =
excerpt.buffer_range().start.to_offset(&excerpt.buffer());
excerpt
.buffer
.runnable_ranges(excerpt.range.context.clone())
.buffer()
.runnable_ranges(excerpt.buffer_range())
.filter_map(move |mut runnable| {
// Re-base onto the excerpts coordinates in the multibuffer
//
@ -3688,15 +3714,14 @@ impl MultiBufferSnapshot {
if runnable.run_range.start < excerpt_buffer_start {
return None;
}
if language::ToPoint::to_point(&runnable.run_range.end, &excerpt.buffer).row
> excerpt.max_buffer_row
if language::ToPoint::to_point(&runnable.run_range.end, &excerpt.buffer())
.row
> excerpt.max_buffer_row()
{
return None;
}
runnable.run_range.start =
excerpt_offset + runnable.run_range.start - excerpt_buffer_start;
runnable.run_range.end =
excerpt_offset + runnable.run_range.end - excerpt_buffer_start;
runnable.run_range = excerpt.map_range_from_buffer(runnable.run_range);
Some(runnable)
})
.skip_while(move |runnable| runnable.run_range.end < range.start)
@ -3730,15 +3755,15 @@ impl MultiBufferSnapshot {
let range = range.start.to_offset(self)..range.end.to_offset(self);
self.excerpts_for_range(range.clone())
.flat_map(move |(excerpt, excerpt_offset)| {
.flat_map(move |excerpt| {
let excerpt_buffer_start_row =
excerpt.range.context.start.to_point(&excerpt.buffer).row;
let excerpt_offset_row = crate::ToPoint::to_point(&excerpt_offset, self).row;
excerpt.buffer_range().start.to_point(&excerpt.buffer()).row;
let excerpt_offset_row = excerpt.start_point().row;
excerpt
.buffer
.buffer()
.indent_guides_in_range(
excerpt.range.context.clone(),
excerpt.buffer_range(),
ignore_disabled_for_language,
cx,
)
@ -3856,151 +3881,6 @@ impl MultiBufferSnapshot {
})
}
pub fn has_git_diffs(&self) -> bool {
for excerpt in self.excerpts.iter() {
if excerpt.buffer.has_git_diff() {
return true;
}
}
false
}
pub fn git_diff_hunks_in_range_rev(
&self,
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
let mut cursor = self.excerpts.cursor::<Point>(&());
cursor.seek(&Point::new(row_range.end.0, 0), Bias::Left, &());
if cursor.item().is_none() {
cursor.prev(&());
}
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
let multibuffer_start = *cursor.start();
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
if multibuffer_start.row >= row_range.end.0 {
return None;
}
let mut buffer_start = excerpt.range.context.start;
let mut buffer_end = excerpt.range.context.end;
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
if row_range.start.0 > multibuffer_start.row {
let buffer_start_point =
excerpt_start_point + Point::new(row_range.start.0 - multibuffer_start.row, 0);
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
}
if row_range.end.0 < multibuffer_end.row {
let buffer_end_point =
excerpt_start_point + Point::new(row_range.end.0 - multibuffer_start.row, 0);
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
}
let buffer_hunks = excerpt
.buffer
.git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end)
.map(move |hunk| {
let start = multibuffer_start.row
+ hunk.row_range.start.saturating_sub(excerpt_start_point.row);
let end = multibuffer_start.row
+ hunk
.row_range
.end
.min(excerpt_end_point.row + 1)
.saturating_sub(excerpt_start_point.row);
MultiBufferDiffHunk {
row_range: MultiBufferRow(start)..MultiBufferRow(end),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
buffer_range: hunk.buffer_range.clone(),
buffer_id: excerpt.buffer_id,
}
});
cursor.prev(&());
Some(buffer_hunks)
})
.flatten()
}
pub fn git_diff_hunks_in_range(
&self,
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
let mut cursor = self.excerpts.cursor::<Point>(&());
cursor.seek(&Point::new(row_range.start.0, 0), Bias::Left, &());
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
let multibuffer_start = *cursor.start();
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
let mut buffer_start = excerpt.range.context.start;
let mut buffer_end = excerpt.range.context.end;
let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end.0) {
cmp::Ordering::Less => {
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
if row_range.start.0 > multibuffer_start.row {
let buffer_start_point = excerpt_start_point
+ Point::new(row_range.start.0 - multibuffer_start.row, 0);
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
}
if row_range.end.0 < multibuffer_end.row {
let buffer_end_point = excerpt_start_point
+ Point::new(row_range.end.0 - multibuffer_start.row, 0);
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
}
excerpt_start_point.row..excerpt_end_point.row
}
cmp::Ordering::Equal if row_range.end.0 == 0 => {
buffer_end = buffer_start;
0..0
}
cmp::Ordering::Greater | cmp::Ordering::Equal => return None,
};
let buffer_hunks = excerpt
.buffer
.git_diff_hunks_intersecting_range(buffer_start..buffer_end)
.map(move |hunk| {
let buffer_range = if excerpt_rows.start == 0 && excerpt_rows.end == 0 {
MultiBufferRow(0)..MultiBufferRow(1)
} else {
let start = multibuffer_start.row
+ hunk.row_range.start.saturating_sub(excerpt_rows.start);
let end = multibuffer_start.row
+ hunk
.row_range
.end
.min(excerpt_rows.end + 1)
.saturating_sub(excerpt_rows.start);
MultiBufferRow(start)..MultiBufferRow(end)
};
MultiBufferDiffHunk {
row_range: buffer_range,
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
buffer_range: hunk.buffer_range.clone(),
buffer_id: excerpt.buffer_id,
}
});
cursor.next(&());
Some(buffer_hunks)
})
.flatten()
}
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let excerpt = self.excerpt_containing(range.clone())?;
@ -4179,7 +4059,7 @@ impl MultiBufferSnapshot {
pub fn excerpt_containing<T: ToOffset>(&self, range: Range<T>) -> Option<MultiBufferExcerpt> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>(&());
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.seek(&range.start, Bias::Right, &());
let start_excerpt = cursor.item()?;
@ -4204,12 +4084,12 @@ impl MultiBufferSnapshot {
I: IntoIterator<Item = Range<Anchor>> + 'a,
{
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
let mut cursor = self.excerpts.cursor::<usize>(&());
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.next(&());
let mut current_range = ranges.next();
iter::from_fn(move || {
let range = current_range.clone()?;
if range.start >= cursor.end(&()) {
if range.start >= cursor.end(&()).0 {
cursor.seek_forward(&range.start, Bias::Right, &());
if range.start == self.len() {
cursor.prev(&());
@ -4217,11 +4097,11 @@ impl MultiBufferSnapshot {
}
let excerpt = cursor.item()?;
let range_start_in_excerpt = cmp::max(range.start, *cursor.start());
let range_start_in_excerpt = cmp::max(range.start, cursor.start().0);
let range_end_in_excerpt = if excerpt.has_trailing_newline {
cmp::min(range.end, cursor.end(&()) - 1)
cmp::min(range.end, cursor.end(&()).0 - 1)
} else {
cmp::min(range.end, cursor.end(&()))
cmp::min(range.end, cursor.end(&()).0)
};
let buffer_range = MultiBufferExcerpt::new(excerpt, *cursor.start())
.map_range_to_buffer(range_start_in_excerpt..range_end_in_excerpt);
@ -4237,7 +4117,7 @@ impl MultiBufferSnapshot {
text_anchor: excerpt.buffer.anchor_after(buffer_range.end),
};
if range.end > cursor.end(&()) {
if range.end > cursor.end(&()).0 {
cursor.next(&());
} else {
current_range = ranges.next();
@ -4256,12 +4136,12 @@ impl MultiBufferSnapshot {
ranges: impl IntoIterator<Item = Range<Anchor>>,
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
let mut cursor = self.excerpts.cursor::<usize>(&());
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.next(&());
let mut current_range = ranges.next();
iter::from_fn(move || {
let range = current_range.clone()?;
if range.start >= cursor.end(&()) {
if range.start >= cursor.end(&()).0 {
cursor.seek_forward(&range.start, Bias::Right, &());
if range.start == self.len() {
cursor.prev(&());
@ -4269,16 +4149,16 @@ impl MultiBufferSnapshot {
}
let excerpt = cursor.item()?;
let range_start_in_excerpt = cmp::max(range.start, *cursor.start());
let range_start_in_excerpt = cmp::max(range.start, cursor.start().0);
let range_end_in_excerpt = if excerpt.has_trailing_newline {
cmp::min(range.end, cursor.end(&()) - 1)
cmp::min(range.end, cursor.end(&()).0 - 1)
} else {
cmp::min(range.end, cursor.end(&()))
cmp::min(range.end, cursor.end(&()).0)
};
let buffer_range = MultiBufferExcerpt::new(excerpt, *cursor.start())
.map_range_to_buffer(range_start_in_excerpt..range_end_in_excerpt);
if range.end > cursor.end(&()) {
if range.end > cursor.end(&()).0 {
cursor.next(&());
} else {
current_range = ranges.next();
@ -4702,6 +4582,11 @@ impl Excerpt {
self.range.context.start.to_offset(&self.buffer)
}
/// The [`Excerpt`]'s start point in its [`Buffer`]
fn buffer_start_point(&self) -> Point {
self.range.context.start.to_point(&self.buffer)
}
/// The [`Excerpt`]'s end offset in its [`Buffer`]
fn buffer_end_offset(&self) -> usize {
self.buffer_start_offset() + self.text_summary.len
@ -4709,10 +4594,11 @@ impl Excerpt {
}
impl<'a> MultiBufferExcerpt<'a> {
fn new(excerpt: &'a Excerpt, excerpt_offset: usize) -> Self {
fn new(excerpt: &'a Excerpt, (excerpt_offset, excerpt_position): (usize, Point)) -> Self {
MultiBufferExcerpt {
excerpt,
excerpt_offset,
excerpt_position,
}
}
@ -4740,9 +4626,32 @@ impl<'a> MultiBufferExcerpt<'a> {
&self.excerpt.buffer
}
pub fn buffer_range(&self) -> Range<text::Anchor> {
self.excerpt.range.context.clone()
}
pub fn start_offset(&self) -> usize {
self.excerpt_offset
}
pub fn start_point(&self) -> Point {
self.excerpt_position
}
/// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`]
pub fn map_offset_to_buffer(&self, offset: usize) -> usize {
self.excerpt.buffer_start_offset() + offset.saturating_sub(self.excerpt_offset)
self.excerpt.buffer_start_offset()
+ offset
.saturating_sub(self.excerpt_offset)
.min(self.excerpt.text_summary.len)
}
/// Maps a point within the [`MultiBuffer`] to a point within the [`Buffer`]
pub fn map_point_to_buffer(&self, point: Point) -> Point {
self.excerpt.buffer_start_point()
+ point
.saturating_sub(self.excerpt_position)
.min(self.excerpt.text_summary.lines)
}
/// Maps a range within the [`MultiBuffer`] to a range within the [`Buffer`]
@ -4752,14 +4661,20 @@ impl<'a> MultiBufferExcerpt<'a> {
/// Map an offset within the [`Buffer`] to an offset within the [`MultiBuffer`]
pub fn map_offset_from_buffer(&self, buffer_offset: usize) -> usize {
let mut buffer_offset_in_excerpt =
buffer_offset.saturating_sub(self.excerpt.buffer_start_offset());
buffer_offset_in_excerpt =
cmp::min(buffer_offset_in_excerpt, self.excerpt.text_summary.len);
let buffer_offset_in_excerpt = buffer_offset
.saturating_sub(self.excerpt.buffer_start_offset())
.min(self.excerpt.text_summary.len);
self.excerpt_offset + buffer_offset_in_excerpt
}
/// Map a point within the [`Buffer`] to a point within the [`MultiBuffer`]
pub fn map_point_from_buffer(&self, buffer_position: Point) -> Point {
let position_in_excerpt = buffer_position.saturating_sub(self.excerpt.buffer_start_point());
let position_in_excerpt =
position_in_excerpt.min(self.excerpt.text_summary.lines + Point::new(1, 0));
self.excerpt_position + position_in_excerpt
}
/// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`]
pub fn map_range_from_buffer(&self, buffer_range: Range<usize>) -> Range<usize> {
self.map_offset_from_buffer(buffer_range.start)
@ -4771,6 +4686,10 @@ impl<'a> MultiBufferExcerpt<'a> {
range.start >= self.excerpt.buffer_start_offset()
&& range.end <= self.excerpt.buffer_end_offset()
}
pub fn max_buffer_row(&self) -> u32 {
self.excerpt.max_buffer_row
}
}
impl ExcerptId {