mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-30 14:17:02 +00:00
df41435d1a
Part of https://github.com/zed-industries/zed/issues/8081 To avoid confusion and bugs when converting between various row `u32`'s, use different types for each. Further PRs should split `Point` into buffer and multi buffer variants and make the code more readable. Release Notes: - N/A --------- Co-authored-by: Piotr <piotr@zed.dev>
309 lines
9.7 KiB
Rust
309 lines
9.7 KiB
Rust
pub mod blame;
|
|
|
|
use std::ops::Range;
|
|
|
|
use git::diff::{DiffHunk, DiffHunkStatus};
|
|
use language::Point;
|
|
use multi_buffer::{Anchor, MultiBufferRow};
|
|
|
|
use crate::{
|
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
|
hunk_status, AnchorRangeExt, DisplayRow,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum DisplayDiffHunk {
|
|
Folded {
|
|
display_row: DisplayRow,
|
|
},
|
|
|
|
Unfolded {
|
|
diff_base_byte_range: Range<usize>,
|
|
display_row_range: Range<DisplayRow>,
|
|
multi_buffer_range: Range<Anchor>,
|
|
status: DiffHunkStatus,
|
|
},
|
|
}
|
|
|
|
impl DisplayDiffHunk {
|
|
pub fn start_display_row(&self) -> DisplayRow {
|
|
match self {
|
|
&DisplayDiffHunk::Folded { display_row } => display_row,
|
|
DisplayDiffHunk::Unfolded {
|
|
display_row_range, ..
|
|
} => display_row_range.start,
|
|
}
|
|
}
|
|
|
|
pub fn contains_display_row(&self, display_row: DisplayRow) -> bool {
|
|
let range = match self {
|
|
&DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
|
|
|
|
DisplayDiffHunk::Unfolded {
|
|
display_row_range, ..
|
|
} => display_row_range.start..=display_row_range.end,
|
|
};
|
|
|
|
range.contains(&display_row)
|
|
}
|
|
}
|
|
|
|
pub fn diff_hunk_to_display(
|
|
hunk: &DiffHunk<MultiBufferRow>,
|
|
snapshot: &DisplaySnapshot,
|
|
) -> DisplayDiffHunk {
|
|
let hunk_start_point = Point::new(hunk.associated_range.start.0, 0);
|
|
let hunk_start_point_sub = Point::new(hunk.associated_range.start.0.saturating_sub(1), 0);
|
|
let hunk_end_point_sub = Point::new(
|
|
hunk.associated_range
|
|
.end
|
|
.0
|
|
.saturating_sub(1)
|
|
.max(hunk.associated_range.start.0),
|
|
0,
|
|
);
|
|
|
|
let status = hunk_status(hunk);
|
|
let is_removal = status == DiffHunkStatus::Removed;
|
|
|
|
let folds_start = Point::new(hunk.associated_range.start.0.saturating_sub(2), 0);
|
|
let folds_end = Point::new(hunk.associated_range.end.0 + 2, 0);
|
|
let folds_range = folds_start..folds_end;
|
|
|
|
let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
|
|
let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
|
|
let fold_point_range = fold_point_range.start..=fold_point_range.end;
|
|
|
|
let folded_start = fold_point_range.contains(&hunk_start_point);
|
|
let folded_end = fold_point_range.contains(&hunk_end_point_sub);
|
|
let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
|
|
|
|
(folded_start && folded_end) || (is_removal && folded_start_sub)
|
|
});
|
|
|
|
if let Some(fold) = containing_fold {
|
|
let row = fold.range.start.to_display_point(snapshot).row();
|
|
DisplayDiffHunk::Folded { display_row: row }
|
|
} else {
|
|
let start = hunk_start_point.to_display_point(snapshot).row();
|
|
|
|
let hunk_end_row = hunk.associated_range.end.max(hunk.associated_range.start);
|
|
let hunk_end_point = Point::new(hunk_end_row.0, 0);
|
|
|
|
let multi_buffer_start = snapshot.buffer_snapshot.anchor_after(hunk_start_point);
|
|
let multi_buffer_end = snapshot.buffer_snapshot.anchor_before(hunk_end_point);
|
|
let end = hunk_end_point.to_display_point(snapshot).row();
|
|
|
|
DisplayDiffHunk::Unfolded {
|
|
display_row_range: start..end,
|
|
multi_buffer_range: multi_buffer_start..multi_buffer_end,
|
|
status,
|
|
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::Point;
|
|
use crate::{editor_tests::init_test, hunk_status};
|
|
use gpui::{Context, TestAppContext};
|
|
use language::Capability::ReadWrite;
|
|
use multi_buffer::{ExcerptRange, MultiBuffer, MultiBufferRow};
|
|
use project::{FakeFs, Project};
|
|
use unindent::Unindent;
|
|
#[gpui::test]
|
|
async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
|
use git::diff::DiffHunkStatus;
|
|
init_test(cx, |_| {});
|
|
|
|
let fs = FakeFs::new(cx.background_executor.clone());
|
|
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,
|
|
);
|
|
});
|
|
|
|
// 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,
|
|
);
|
|
});
|
|
|
|
cx.background_executor.run_until_parked();
|
|
|
|
let multibuffer = cx.new_model(|cx| {
|
|
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
|
multibuffer.push_excerpts(
|
|
buffer_1.clone(),
|
|
[
|
|
// excerpt ends in the middle of a modified hunk
|
|
ExcerptRange {
|
|
context: Point::new(0, 0)..Point::new(1, 5),
|
|
primary: Default::default(),
|
|
},
|
|
// excerpt begins in the middle of a modified hunk
|
|
ExcerptRange {
|
|
context: Point::new(5, 0)..Point::new(6, 5),
|
|
primary: Default::default(),
|
|
},
|
|
],
|
|
cx,
|
|
);
|
|
multibuffer.push_excerpts(
|
|
buffer_2.clone(),
|
|
[
|
|
// excerpt ends at a deletion
|
|
ExcerptRange {
|
|
context: Point::new(0, 0)..Point::new(1, 5),
|
|
primary: Default::default(),
|
|
},
|
|
// excerpt starts at a deletion
|
|
ExcerptRange {
|
|
context: Point::new(2, 0)..Point::new(2, 5),
|
|
primary: Default::default(),
|
|
},
|
|
// excerpt fully contains a deletion hunk
|
|
ExcerptRange {
|
|
context: Point::new(1, 0)..Point::new(2, 5),
|
|
primary: Default::default(),
|
|
},
|
|
// excerpt fully contains an insertion hunk
|
|
ExcerptRange {
|
|
context: Point::new(4, 0)..Point::new(6, 5),
|
|
primary: Default::default(),
|
|
},
|
|
],
|
|
cx,
|
|
);
|
|
multibuffer
|
|
});
|
|
|
|
let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
|
|
|
|
assert_eq!(
|
|
snapshot.text(),
|
|
"
|
|
1.zero
|
|
1.ONE
|
|
1.FIVE
|
|
1.six
|
|
2.zero
|
|
2.one
|
|
2.two
|
|
2.one
|
|
2.two
|
|
2.four
|
|
2.five
|
|
2.six"
|
|
.unindent()
|
|
);
|
|
|
|
let expected = [
|
|
(
|
|
DiffHunkStatus::Modified,
|
|
MultiBufferRow(1)..MultiBufferRow(2),
|
|
),
|
|
(
|
|
DiffHunkStatus::Modified,
|
|
MultiBufferRow(2)..MultiBufferRow(3),
|
|
),
|
|
//TODO: Define better when and where removed hunks show up at range extremities
|
|
(
|
|
DiffHunkStatus::Removed,
|
|
MultiBufferRow(6)..MultiBufferRow(6),
|
|
),
|
|
(
|
|
DiffHunkStatus::Removed,
|
|
MultiBufferRow(8)..MultiBufferRow(8),
|
|
),
|
|
(
|
|
DiffHunkStatus::Added,
|
|
MultiBufferRow(10)..MultiBufferRow(11),
|
|
),
|
|
];
|
|
|
|
assert_eq!(
|
|
snapshot
|
|
.git_diff_hunks_in_range(MultiBufferRow(0)..MultiBufferRow(12))
|
|
.map(|hunk| (hunk_status(&hunk), hunk.associated_range))
|
|
.collect::<Vec<_>>(),
|
|
&expected,
|
|
);
|
|
|
|
assert_eq!(
|
|
snapshot
|
|
.git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
|
|
.map(|hunk| (hunk_status(&hunk), hunk.associated_range))
|
|
.collect::<Vec<_>>(),
|
|
expected
|
|
.iter()
|
|
.rev()
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
.as_slice(),
|
|
);
|
|
}
|
|
}
|