@@ -1,309 +1 @@
pub mod blame;
-
-use std::ops::Range;
-
-use git::diff::DiffHunkStatus;
-use language::Point;
-use multi_buffer::{Anchor, MultiBufferDiffHunk};
-
-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: &MultiBufferDiffHunk,
- snapshot: &DisplaySnapshot,
-) -> DisplayDiffHunk {
- let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
- let hunk_start_point_sub = Point::new(hunk.row_range.start.0.saturating_sub(1), 0);
- let hunk_end_point_sub = Point::new(
- hunk.row_range
- .end
- .0
- .saturating_sub(1)
- .max(hunk.row_range.start.0),
- 0,
- );
-
- let status = hunk_status(hunk);
- let is_removal = status == DiffHunkStatus::Removed;
-
- let folds_start = Point::new(hunk.row_range.start.0.saturating_sub(2), 0);
- let folds_end = Point::new(hunk.row_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.row_range.end.max(hunk.row_range.start);
- let hunk_end_point = Point::new(hunk_end_row.0, 0);
-
- let multi_buffer_start = snapshot.buffer_snapshot.anchor_before(hunk_start_point);
- let multi_buffer_end = snapshot.buffer_snapshot.anchor_after(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(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.row_range))
- .collect::<Vec<_>>(),
- &expected,
- );
-
- assert_eq!(
- snapshot
- .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
- .map(|hunk| (hunk_status(&hunk), hunk.row_range))
- .collect::<Vec<_>>(),
- expected
- .iter()
- .rev()
- .cloned()
- .collect::<Vec<_>>()
- .as_slice(),
- );
- }
-}
@@ -1,18 +1,16 @@
-use std::{
- ops::{Range, RangeInclusive},
- sync::Arc,
-};
-
use collections::{hash_map, HashMap, HashSet};
use git::diff::DiffHunkStatus;
use gpui::{Action, AppContext, CursorStyle, Hsla, Model, MouseButton, Subscription, Task, View};
-use language::Buffer;
+use language::{Buffer, BufferId, Point};
use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
MultiBufferSnapshot, ToPoint,
};
use settings::SettingsStore;
-use text::{BufferId, Point};
+use std::{
+ ops::{Range, RangeInclusive},
+ sync::Arc,
+};
use ui::{
prelude::*, ActiveTheme, ContextMenu, InteractiveElement, IntoElement, ParentElement, Pixels,
Styled, ViewContext, VisualContext,
@@ -20,13 +18,11 @@ use ui::{
use util::{debug_panic, RangeExt};
use crate::{
- editor_settings::CurrentLineHighlight,
- git::{diff_hunk_to_display, DisplayDiffHunk},
- hunk_status, hunks_for_selections,
- mouse_context_menu::MouseContextMenu,
- BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, Editor,
- EditorElement, EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertFile,
- RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
+ editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections,
+ mouse_context_menu::MouseContextMenu, BlockDisposition, BlockProperties, BlockStyle,
+ CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot, Editor, EditorElement,
+ EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertFile, RevertSelectedHunks,
+ ToDisplayPoint, ToggleHunkDiff,
};
#[derive(Debug, Clone)]
@@ -43,12 +39,35 @@ pub(super) struct ExpandedHunks {
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
}
+#[derive(Debug, Clone)]
+pub(super) struct ExpandedHunk {
+ pub block: Option<CustomBlockId>,
+ pub hunk_range: Range<Anchor>,
+ pub diff_base_byte_range: Range<usize>,
+ pub status: DiffHunkStatus,
+ pub folded: bool,
+}
+
#[derive(Debug)]
struct DiffBaseBuffer {
buffer: Model<Buffer>,
diff_base_version: usize,
}
+#[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 ExpandedHunks {
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
self.hunks
@@ -57,15 +76,6 @@ impl ExpandedHunks {
}
}
-#[derive(Debug, Clone)]
-pub(super) struct ExpandedHunk {
- pub block: Option<CustomBlockId>,
- pub hunk_range: Range<Anchor>,
- pub diff_base_byte_range: Range<usize>,
- pub status: DiffHunkStatus,
- pub folded: bool,
-}
-
impl Editor {
pub(super) fn open_hunk_context_menu(
&mut self,
@@ -883,3 +893,287 @@ fn to_inclusive_row_range(
let new_range = point_range.to_anchors(&snapshot.buffer_snapshot);
new_range.start..=new_range.end
}
+
+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: &MultiBufferDiffHunk,
+ snapshot: &DisplaySnapshot,
+) -> DisplayDiffHunk {
+ let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
+ let hunk_start_point_sub = Point::new(hunk.row_range.start.0.saturating_sub(1), 0);
+ let hunk_end_point_sub = Point::new(
+ hunk.row_range
+ .end
+ .0
+ .saturating_sub(1)
+ .max(hunk.row_range.start.0),
+ 0,
+ );
+
+ let status = hunk_status(hunk);
+ let is_removal = status == DiffHunkStatus::Removed;
+
+ let folds_start = Point::new(hunk.row_range.start.0.saturating_sub(2), 0);
+ let folds_end = Point::new(hunk.row_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.row_range.end.max(hunk.row_range.start);
+ let hunk_end_point = Point::new(hunk_end_row.0, 0);
+
+ let multi_buffer_start = snapshot.buffer_snapshot.anchor_before(hunk_start_point);
+ let multi_buffer_end = snapshot.buffer_snapshot.anchor_after(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 super::*;
+ 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 as _;
+
+ #[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(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.row_range))
+ .collect::<Vec<_>>(),
+ &expected,
+ );
+
+ assert_eq!(
+ snapshot
+ .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
+ .map(|hunk| (hunk_status(&hunk), hunk.row_range))
+ .collect::<Vec<_>>(),
+ expected
+ .iter()
+ .rev()
+ .cloned()
+ .collect::<Vec<_>>()
+ .as_slice(),
+ );
+ }
+}