diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8b12c2be50..75c15f0e09 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -52,7 +52,7 @@ pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit}; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context as _, Result}; use blink_manager::BlinkManager; -use buffer_diff::DiffHunkSecondaryStatus; +use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus}; use client::{Collaborator, ParticipantIndex}; use clock::ReplicaId; use collections::{BTreeMap, HashMap, HashSet, VecDeque}; @@ -253,6 +253,19 @@ impl Navigated { } } +#[derive(Debug, Clone, PartialEq, Eq)] +enum DisplayDiffHunk { + Folded { + display_row: DisplayRow, + }, + Unfolded { + diff_base_byte_range: Range, + display_row_range: Range, + multi_buffer_range: Range, + status: DiffHunkStatus, + }, +} + pub fn init_settings(cx: &mut App) { EditorSettings::register(cx); } @@ -16915,6 +16928,52 @@ impl EditorSnapshot { hunks } + fn display_diff_hunks_for_rows<'a>( + &'a self, + display_rows: Range, + folded_buffers: &'a HashSet, + ) -> impl 'a + Iterator { + let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self); + let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self); + + self.buffer_snapshot + .diff_hunks_in_range(buffer_start..buffer_end) + .filter_map(|hunk| { + if folded_buffers.contains(&hunk.buffer_id) { + return None; + } + + let hunk_start_point = Point::new(hunk.row_range.start.0, 0); + let hunk_end_point = Point::new(hunk.row_range.end.0, 0); + + let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left); + let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right); + + let display_hunk = if hunk_display_start.column() != 0 { + DisplayDiffHunk::Folded { + display_row: hunk_display_start.row(), + } + } else { + let mut end_row = hunk_display_end.row(); + if hunk_display_end.column() > 0 { + end_row.0 += 1; + } + DisplayDiffHunk::Unfolded { + status: hunk.status(), + diff_base_byte_range: hunk.diff_base_byte_range, + display_row_range: hunk_display_start.row()..end_row, + multi_buffer_range: Anchor::range_in_buffer( + hunk.excerpt_id, + hunk.buffer_id, + hunk.buffer_range, + ), + } + }; + + Some(display_hunk) + }) + } + pub fn language_at(&self, position: T) -> Option<&Arc> { self.display_snapshot.buffer_snapshot.language_at(position) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ebcff06152..8e81c2560b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -24,7 +24,7 @@ use language::{ Override, Point, }; use language_settings::{Formatter, FormatterList, IndentGuideSettings}; -use multi_buffer::IndentGuide; +use multi_buffer::{IndentGuide, PathKey}; use parking_lot::Mutex; use pretty_assertions::{assert_eq, assert_ne}; use project::project_settings::{LspSettings, ProjectSettings}; @@ -36,6 +36,7 @@ use std::{ sync::atomic::{self, AtomicUsize}, }; use test::{build_editor_with_project, editor_lsp_test_context::rust_lang}; +use text::ToPoint as _; use unindent::Unindent; use util::{ assert_set_eq, path, @@ -14983,6 +14984,94 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp ); } +#[gpui::test] +async fn test_display_diff_hunks(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/test"), + json!({ + ".git": {}, + "file-1": "ONE\n", + "file-2": "TWO\n", + "file-3": "THREE\n", + }), + ) + .await; + + fs.set_head_for_repo( + path!("/test/.git").as_ref(), + &[ + ("file-1".into(), "one\n".into()), + ("file-2".into(), "two\n".into()), + ("file-3".into(), "three\n".into()), + ], + ); + + let project = Project::test(fs, [path!("/test").as_ref()], cx).await; + let mut buffers = vec![]; + for i in 1..=3 { + let buffer = project + .update(cx, |project, cx| { + let path = format!(path!("/test/file-{}"), i); + project.open_local_buffer(path, cx) + }) + .await + .unwrap(); + buffers.push(buffer); + } + + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(Capability::ReadWrite); + multibuffer.set_all_diff_hunks_expanded(cx); + for buffer in &buffers { + let snapshot = buffer.read(cx).snapshot(); + multibuffer.set_excerpts_for_path( + PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()), + buffer.clone(), + vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)], + DEFAULT_MULTIBUFFER_CONTEXT, + cx, + ); + } + multibuffer + }); + + let editor = cx.add_window(|window, cx| { + Editor::new( + EditorMode::Full, + multibuffer, + Some(project), + true, + window, + cx, + ) + }); + cx.run_until_parked(); + + let snapshot = editor + .update(cx, |editor, window, cx| editor.snapshot(window, cx)) + .unwrap(); + let hunks = snapshot + .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default()) + .map(|hunk| match hunk { + DisplayDiffHunk::Unfolded { + display_row_range, .. + } => display_row_range, + DisplayDiffHunk::Folded { .. } => unreachable!(), + }) + .collect::>(); + assert_eq!( + hunks, + [ + DisplayRow(3)..DisplayRow(5), + DisplayRow(10)..DisplayRow(12), + DisplayRow(17)..DisplayRow(19), + ] + ); +} + #[gpui::test] async fn test_partially_staged_hunk(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 89baa3d932..3a80b45297 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -15,8 +15,8 @@ use crate::{ items::BufferSearchHighlights, mouse_context_menu::{self, MenuPosition, MouseContextMenu}, scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair}, - BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow, - DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, + BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint, + DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk, GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, @@ -79,19 +79,6 @@ use workspace::{item::Item, notifications::NotifyTaskExt}; const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.; const MIN_SCROLL_THUMB_SIZE: f32 = 25.; -#[derive(Debug, Clone, PartialEq, Eq)] -enum DisplayDiffHunk { - Folded { - display_row: DisplayRow, - }, - Unfolded { - diff_base_byte_range: Range, - display_row_range: Range, - multi_buffer_range: Range, - status: DiffHunkStatus, - }, -} - struct SelectionLayout { head: DisplayPoint, cursor_shape: CursorShape, @@ -1553,50 +1540,11 @@ impl EditorElement { window: &mut Window, cx: &mut App, ) -> Vec<(DisplayDiffHunk, Option)> { - let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(snapshot); - let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(snapshot); - - let mut display_hunks = Vec::<(DisplayDiffHunk, Option)>::new(); let folded_buffers = self.editor.read(cx).folded_buffers(cx); - - for hunk in snapshot - .buffer_snapshot - .diff_hunks_in_range(buffer_start..buffer_end) - { - if folded_buffers.contains(&hunk.buffer_id) { - continue; - } - - let hunk_start_point = Point::new(hunk.row_range.start.0, 0); - let hunk_end_point = Point::new(hunk.row_range.end.0, 0); - - let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left); - let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right); - - let display_hunk = if hunk_display_start.column() != 0 { - DisplayDiffHunk::Folded { - display_row: hunk_display_start.row(), - } - } else { - let mut end_row = hunk_display_end.row(); - if hunk_display_end.column() > 0 { - end_row.0 += 1; - } - DisplayDiffHunk::Unfolded { - status: hunk.status(), - diff_base_byte_range: hunk.diff_base_byte_range, - display_row_range: hunk_display_start.row()..end_row, - multi_buffer_range: Anchor::range_in_buffer( - hunk.excerpt_id, - hunk.buffer_id, - hunk.buffer_range, - ), - } - }; - - display_hunks.push((display_hunk, None)); - } - + let mut display_hunks = snapshot + .display_diff_hunks_for_rows(display_rows, folded_buffers) + .map(|hunk| (hunk, None)) + .collect::>(); let git_gutter_setting = ProjectSettings::get_global(cx) .git .git_gutter diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 34988a6b2f..01c75c5470 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6146,6 +6146,16 @@ where || self.diff_transforms.item().is_none() { self.excerpts.next(&()); + } else if let Some(DiffTransform::DeletedHunk { hunk_info, .. }) = + self.diff_transforms.item() + { + if self + .excerpts + .item() + .map_or(false, |excerpt| excerpt.id != hunk_info.excerpt_id) + { + self.excerpts.next(&()); + } } } } @@ -6183,12 +6193,13 @@ where return true; } - self.diff_transforms.next(&()); - let next_transform = self.diff_transforms.item(); - self.diff_transforms.prev(&()); - - next_transform.map_or(true, |next_transform| { - matches!(next_transform, DiffTransform::BufferContent { .. }) + let next_transform = self.diff_transforms.next_item(); + next_transform.map_or(true, |next_transform| match next_transform { + DiffTransform::BufferContent { .. } => true, + DiffTransform::DeletedHunk { hunk_info, .. } => self + .excerpts + .item() + .map_or(false, |excerpt| excerpt.id != hunk_info.excerpt_id), }) }