Basic side-by-side diff implementation (#43586)
Release Notes: - N/A --------- Co-authored-by: cameron <cameron.studdstreet@gmail.com> Co-authored-by: Cameron <cameron@zed.dev>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use futures::channel::oneshot;
|
||||
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use language::{BufferRow, Language, LanguageRegistry};
|
||||
use rope::Rope;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
@@ -11,7 +11,7 @@ use std::{
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
|
||||
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
|
||||
use util::ResultExt;
|
||||
|
||||
pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
|
||||
@@ -88,6 +88,7 @@ struct PendingHunk {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl sum_tree::Item for InternalDiffHunk {
|
||||
@@ -96,6 +97,7 @@ impl sum_tree::Item for InternalDiffHunk {
|
||||
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
diff_base_byte_range: self.diff_base_byte_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +108,7 @@ impl sum_tree::Item for PendingHunk {
|
||||
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
diff_base_byte_range: self.diff_base_byte_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +119,7 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
diff_base_byte_range: 0..0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +129,15 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
.start
|
||||
.min(&other.buffer_range.start, buffer);
|
||||
self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
|
||||
|
||||
self.diff_base_byte_range.start = self
|
||||
.diff_base_byte_range
|
||||
.start
|
||||
.min(other.diff_base_byte_range.start);
|
||||
self.diff_base_byte_range.end = self
|
||||
.diff_base_byte_range
|
||||
.end
|
||||
.max(other.diff_base_byte_range.end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +318,54 @@ impl BufferDiffSnapshot {
|
||||
let (new_id, new_empty) = (right.remote_id(), right.is_empty());
|
||||
new_id == old_id || (new_empty && old_empty)
|
||||
}
|
||||
|
||||
pub fn row_to_base_text_row(&self, row: BufferRow, buffer: &text::BufferSnapshot) -> u32 {
|
||||
// TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
|
||||
|
||||
// Find the last hunk that starts before this position.
|
||||
let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
|
||||
let position = buffer.anchor_before(Point::new(row, 0));
|
||||
cursor.seek(&position, Bias::Left);
|
||||
if cursor
|
||||
.item()
|
||||
.is_none_or(|hunk| hunk.buffer_range.start.cmp(&position, buffer).is_gt())
|
||||
{
|
||||
cursor.prev();
|
||||
}
|
||||
|
||||
let unclipped_point = if let Some(hunk) = cursor.item()
|
||||
&& hunk.buffer_range.start.cmp(&position, buffer).is_le()
|
||||
{
|
||||
let mut unclipped_point = cursor
|
||||
.end()
|
||||
.diff_base_byte_range
|
||||
.end
|
||||
.to_point(self.base_text());
|
||||
if position.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
|
||||
unclipped_point +=
|
||||
Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
|
||||
}
|
||||
// Move the cursor so that at the next step we can clip with the start of the next hunk.
|
||||
cursor.next();
|
||||
unclipped_point
|
||||
} else {
|
||||
// Position is before the added region for the first hunk.
|
||||
debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
|
||||
position.cmp(&first_hunk.buffer_range.start, buffer).is_le()
|
||||
}));
|
||||
Point::new(row, 0)
|
||||
};
|
||||
|
||||
let max_point = if let Some(next_hunk) = cursor.item() {
|
||||
next_hunk
|
||||
.diff_base_byte_range
|
||||
.start
|
||||
.to_point(self.base_text())
|
||||
} else {
|
||||
self.base_text().max_point()
|
||||
};
|
||||
unclipped_point.min(max_point).row
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferDiffInner {
|
||||
@@ -946,6 +1007,7 @@ impl BufferDiff {
|
||||
if self.secondary_diff.is_some() {
|
||||
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
|
||||
buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
|
||||
diff_base_byte_range: 0..0,
|
||||
});
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
|
||||
@@ -2240,4 +2302,62 @@ mod tests {
|
||||
hunks = found_hunks;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
|
||||
let base_text = "
|
||||
zero
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
"
|
||||
.unindent();
|
||||
let buffer_text = "
|
||||
zero
|
||||
ONE
|
||||
two
|
||||
NINE
|
||||
five
|
||||
seven
|
||||
"
|
||||
.unindent();
|
||||
|
||||
// zero
|
||||
// - one
|
||||
// + ONE
|
||||
// two
|
||||
// - three
|
||||
// - four
|
||||
// + NINE
|
||||
// five
|
||||
// - six
|
||||
// seven
|
||||
// + eight
|
||||
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
|
||||
let expected_results = [
|
||||
// don't format me
|
||||
(0, 0),
|
||||
(1, 2),
|
||||
(2, 2),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(5, 7),
|
||||
(6, 9),
|
||||
];
|
||||
for (buffer_row, expected) in expected_results {
|
||||
assert_eq!(
|
||||
diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
|
||||
expected,
|
||||
"{buffer_row}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user