diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b4ce8c1b92..7ca49a414d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -402,7 +402,9 @@ { "context": "Editor", "bindings": { - "alt-enter": "editor::OpenExcerpts" + "alt-enter": "editor::OpenExcerpts", + "cmd-f8": "editor::GoToHunk", + "cmd-shift-f8": "editor::GoToPrevHunk" } }, { diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 41c9052ebe..166de2b4ac 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -1483,7 +1483,7 @@ async fn test_git_diff_base_change( buffer_local_a.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -1503,7 +1503,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -1527,7 +1527,7 @@ async fn test_git_diff_base_change( assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -1538,7 +1538,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -1581,7 +1581,7 @@ async fn test_git_diff_base_change( buffer_local_b.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -1601,7 +1601,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -1629,12 +1629,12 @@ async fn test_git_diff_base_change( "{:?}", buffer .snapshot() - .git_diff_hunks_in_range(0..4) + .git_diff_hunks_in_range(0..4, false) .collect::>() ); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -1645,7 +1645,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_range(0..4), + buffer.snapshot().git_diff_hunks_in_range(0..4, false), &buffer, &diff_base, &[(2..3, "", "three\n")], diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 327f143db5..d1bd1622b4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,6 +1,7 @@ mod blink_manager; pub mod display_map; mod element; +mod git; mod highlight_matching_bracket; mod hover_popover; pub mod items; @@ -42,6 +43,7 @@ use gpui::{ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; pub use items::MAX_TAB_TITLE_LEN; +use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, @@ -79,6 +81,8 @@ use theme::{DiagnosticStyle, Theme}; use util::{post_inc, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, Workspace}; +use crate::git::diff_hunk_to_display; + const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); const MAX_LINE_LEN: usize = 1024; @@ -162,6 +166,8 @@ actions!( NewlineBelow, GoToDiagnostic, GoToPrevDiagnostic, + GoToHunk, + GoToPrevHunk, Indent, Outdent, DeleteLine, @@ -338,6 +344,8 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::redo_selection); cx.add_action(Editor::go_to_diagnostic); cx.add_action(Editor::go_to_prev_diagnostic); + cx.add_action(Editor::go_to_hunk); + cx.add_action(Editor::go_to_prev_hunk); cx.add_action(Editor::go_to_definition); cx.add_action(Editor::go_to_type_definition); cx.add_action(Editor::fold); @@ -5232,6 +5240,72 @@ impl Editor { } } + fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { + self.go_to_hunk_impl(Direction::Next, cx) + } + + fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { + self.go_to_hunk_impl(Direction::Prev, cx) + } + + pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext) { + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); + + fn seek_in_direction( + this: &mut Editor, + snapshot: &DisplaySnapshot, + initial_point: Point, + is_wrapped: bool, + direction: Direction, + cx: &mut ViewContext, + ) -> bool { + let hunks = if direction == Direction::Next { + snapshot + .buffer_snapshot + .git_diff_hunks_in_range(initial_point.row..u32::MAX, false) + } else { + snapshot + .buffer_snapshot + .git_diff_hunks_in_range(0..initial_point.row, true) + }; + + let display_point = initial_point.to_display_point(snapshot); + let mut hunks = hunks + .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) + .skip_while(|hunk| { + if is_wrapped { + false + } else { + hunk.contains_display_row(display_point.row()) + } + }) + .dedup(); + + if let Some(hunk) = hunks.next() { + this.change_selections(Some(Autoscroll::Center), cx, |s| { + let row = hunk.start_display_row(); + let point = DisplayPoint::new(row, 0); + s.select_display_ranges([point..point]); + }); + + true + } else { + false + } + } + + if !seek_in_direction(self, &snapshot, selection.head(), false, direction, cx) { + let wrapped_point = match direction { + Direction::Next => Point::zero(), + Direction::Prev => snapshot.buffer_snapshot.max_point(), + }; + seek_in_direction(self, &snapshot, wrapped_point, true, direction, cx); + } + } + pub fn go_to_definition( workspace: &mut Workspace, _: &GoToDefinition, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5f26ddb3b8..796b7606f0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11,6 +11,7 @@ use crate::test::{ editor_test_context::EditorTestContext, select_ranges, }; use gpui::{ + executor::Deterministic, geometry::rect::RectF, platform::{WindowBounds, WindowOptions}, }; @@ -5116,6 +5117,111 @@ fn test_combine_syntax_and_fuzzy_match_highlights() { ); } +#[gpui::test] +async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx); + + let diff_base = r#" + use some::mod; + + const A: u32 = 42; + + fn main() { + println!("hello"); + + println!("world"); + } + "# + .unindent(); + + // Edits are modified, removed, modified, added + cx.set_state( + &r#" + use some::modified; + + ˇ + fn main() { + println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); + + cx.set_diff_base(Some(&diff_base)); + deterministic.run_until_parked(); + + cx.update_editor(|editor, cx| { + //Wrap around the bottom of the buffer + for _ in 0..3 { + editor.go_to_hunk(&GoToHunk, cx); + } + }); + + cx.assert_editor_state( + &r#" + ˇuse some::modified; + + + fn main() { + println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); + + cx.update_editor(|editor, cx| { + //Wrap around the top of the buffer + for _ in 0..2 { + editor.go_to_prev_hunk(&GoToPrevHunk, cx); + } + }); + + cx.assert_editor_state( + &r#" + use some::modified; + + + fn main() { + ˇ println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); + + cx.update_editor(|editor, cx| { + editor.fold(&Fold, cx); + + //Make sure that the fold only gets one hunk + for _ in 0..4 { + editor.go_to_hunk(&GoToHunk, cx); + } + }); + + cx.assert_editor_state( + &r#" + ˇuse some::modified; + + + fn main() { + println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d80b4ea844..dc62b2e923 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5,6 +5,7 @@ use super::{ }; use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, + git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, @@ -12,7 +13,7 @@ use crate::{ GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, }, mouse_context_menu::DeployMouseContextMenu, - AnchorRangeExt, EditorStyle, + EditorStyle, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; @@ -33,8 +34,9 @@ use gpui::{ Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle, }; +use itertools::Itertools; use json::json; -use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Point, Selection}; +use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection}; use project::ProjectPath; use settings::{GitGutter, Settings}; use smallvec::SmallVec; @@ -46,13 +48,6 @@ use std::{ sync::Arc, }; -#[derive(Debug)] -struct DiffHunkLayout { - visual_range: Range, - status: DiffHunkStatus, - is_folded: bool, -} - struct SelectionLayout { head: DisplayPoint, cursor_shape: CursorShape, @@ -576,14 +571,41 @@ impl EditorElement { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_top = scroll_position.y() * line_height; - for hunk in &layout.hunk_layouts { - let color = match (hunk.status, hunk.is_folded) { - (DiffHunkStatus::Added, false) => diff_style.inserted, - (DiffHunkStatus::Modified, false) => diff_style.modified, + for hunk in &layout.display_hunks { + let (display_row_range, status) = match hunk { + //TODO: This rendering is entirely a horrible hack + &DisplayDiffHunk::Folded { display_row: row } => { + let start_y = row as f32 * line_height - scroll_top; + let end_y = start_y + line_height; + + let width = diff_style.removed_width_em * line_height; + let highlight_origin = bounds.origin() + vec2f(-width, start_y); + let highlight_size = vec2f(width * 2., end_y - start_y); + let highlight_bounds = RectF::new(highlight_origin, highlight_size); + + cx.scene.push_quad(Quad { + bounds: highlight_bounds, + background: Some(diff_style.modified), + border: Border::new(0., Color::transparent_black()), + corner_radius: 1. * line_height, + }); + + continue; + } + + DisplayDiffHunk::Unfolded { + display_row_range, + status, + } => (display_row_range, status), + }; + + let color = match status { + DiffHunkStatus::Added => diff_style.inserted, + DiffHunkStatus::Modified => diff_style.modified, //TODO: This rendering is entirely a horrible hack - (DiffHunkStatus::Removed, false) => { - let row = hunk.visual_range.start; + DiffHunkStatus::Removed => { + let row = *display_row_range.start(); let offset = line_height / 2.; let start_y = row as f32 * line_height - offset - scroll_top; @@ -603,33 +625,13 @@ impl EditorElement { continue; } - - (_, true) => { - let row = hunk.visual_range.start; - let start_y = row as f32 * line_height - scroll_top; - let end_y = start_y + line_height; - - let width = diff_style.removed_width_em * line_height; - let highlight_origin = bounds.origin() + vec2f(-width, start_y); - let highlight_size = vec2f(width * 2., end_y - start_y); - let highlight_bounds = RectF::new(highlight_origin, highlight_size); - - cx.scene.push_quad(Quad { - bounds: highlight_bounds, - background: Some(diff_style.modified), - border: Border::new(0., Color::transparent_black()), - corner_radius: 1. * line_height, - }); - - continue; - } }; - let start_row = hunk.visual_range.start; - let end_row = hunk.visual_range.end; + let start_row = *display_row_range.start(); + let end_row = *display_row_range.end(); let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top; + let end_y = end_row as f32 * line_height - scroll_top + line_height; let width = diff_style.width_em * line_height; let highlight_origin = bounds.origin() + vec2f(-width, start_y); @@ -1107,69 +1109,23 @@ impl EditorElement { //If a fold contains any hunks then that fold line is marked as modified fn layout_git_gutters( &self, - rows: Range, + display_rows: Range, snapshot: &EditorSnapshot, - ) -> Vec { + ) -> Vec { let buffer_snapshot = &snapshot.buffer_snapshot; - let visual_start = DisplayPoint::new(rows.start, 0).to_point(snapshot).row; - let visual_end = DisplayPoint::new(rows.end, 0).to_point(snapshot).row; - let hunks = buffer_snapshot.git_diff_hunks_in_range(visual_start..visual_end); - let mut layouts = Vec::::new(); + let buffer_start_row = DisplayPoint::new(display_rows.start, 0) + .to_point(snapshot) + .row; + let buffer_end_row = DisplayPoint::new(display_rows.end, 0) + .to_point(snapshot) + .row; - for hunk in hunks { - let hunk_start_point = Point::new(hunk.buffer_range.start, 0); - let hunk_end_point = Point::new(hunk.buffer_range.end, 0); - let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); - let hunk_end_point_sub = Point::new( - hunk.buffer_range - .end - .saturating_sub(1) - .max(hunk.buffer_range.start), - 0, - ); - - let is_removal = hunk.status() == DiffHunkStatus::Removed; - - let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); - let folds_end = Point::new(hunk.buffer_range.end + 1, 0); - let folds_range = folds_start..folds_end; - - let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| { - let fold_point_range = fold_range.to_point(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) - }); - - let visual_range = if let Some(fold) = containing_fold { - let row = fold.start.to_display_point(snapshot).row(); - row..row - } else { - let start = hunk_start_point.to_display_point(snapshot).row(); - let end = hunk_end_point.to_display_point(snapshot).row(); - start..end - }; - - let has_existing_layout = match layouts.last() { - Some(e) => visual_range == e.visual_range && e.status == hunk.status(), - None => false, - }; - - if !has_existing_layout { - layouts.push(DiffHunkLayout { - visual_range, - status: hunk.status(), - is_folded: containing_fold.is_some(), - }); - } - } - - layouts + buffer_snapshot + .git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false) + .map(|hunk| diff_hunk_to_display(hunk, snapshot)) + .dedup() + .collect() } fn layout_line_numbers( @@ -1721,7 +1677,7 @@ impl Element for EditorElement { let line_number_layouts = self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); - let hunk_layouts = self.layout_git_gutters(start_row..end_row, &snapshot); + let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines); @@ -1880,7 +1836,7 @@ impl Element for EditorElement { highlighted_rows, highlighted_ranges, line_number_layouts, - hunk_layouts, + display_hunks, blocks, selections, context_menu, @@ -2002,7 +1958,7 @@ pub struct LayoutState { active_rows: BTreeMap, highlighted_rows: Option>, line_number_layouts: Vec>, - hunk_layouts: Vec, + display_hunks: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Color)>, selections: Vec<(ReplicaId, Vec)>, diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs new file mode 100644 index 0000000000..549d74a0b5 --- /dev/null +++ b/crates/editor/src/git.rs @@ -0,0 +1,93 @@ +use std::ops::RangeInclusive; + +use git::diff::{DiffHunk, DiffHunkStatus}; +use language::Point; + +use crate::{ + display_map::{DisplaySnapshot, ToDisplayPoint}, + AnchorRangeExt, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DisplayDiffHunk { + Folded { + display_row: u32, + }, + + Unfolded { + display_row_range: RangeInclusive, + status: DiffHunkStatus, + }, +} + +impl DisplayDiffHunk { + pub fn start_display_row(&self) -> u32 { + 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: u32) -> bool { + let range = match self { + &DisplayDiffHunk::Folded { display_row } => display_row..=display_row, + + DisplayDiffHunk::Unfolded { + display_row_range, .. + } => display_row_range.clone(), + }; + + range.contains(&display_row) + } +} + +pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> DisplayDiffHunk { + let hunk_start_point = Point::new(hunk.buffer_range.start, 0); + let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); + let hunk_end_point_sub = Point::new( + hunk.buffer_range + .end + .saturating_sub(1) + .max(hunk.buffer_range.start), + 0, + ); + + let is_removal = hunk.status() == DiffHunkStatus::Removed; + + let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0); + let folds_end = Point::new(hunk.buffer_range.end + 2, 0); + let folds_range = folds_start..folds_end; + + let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| { + 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.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_inclusive = hunk + .buffer_range + .end + .saturating_sub(1) + .max(hunk.buffer_range.start); + let hunk_end_point = Point::new(hunk_end_row_inclusive, 0); + let end = hunk_end_point.to_display_point(snapshot).row(); + + DisplayDiffHunk::Unfolded { + display_row_range: start..=end, + status: hunk.status(), + } + } +} diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 1cff003868..38475daf28 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2592,10 +2592,13 @@ impl MultiBufferSnapshot { pub fn git_diff_hunks_in_range<'a>( &'a self, row_range: Range, + reversed: bool, ) -> impl 'a + Iterator> { self.as_singleton() .into_iter() - .flat_map(move |(_, _, buffer)| buffer.git_diff_hunks_in_range(row_range.clone())) + .flat_map(move |(_, _, buffer)| { + buffer.git_diff_hunks_in_range(row_range.clone(), reversed) + }) } pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 999f410db5..fdb1fc44e4 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -594,7 +594,6 @@ impl<'a> MutableSelectionsCollection<'a> { self.select_anchors(selections) } - #[cfg(any(test, feature = "test-support"))] pub fn select_display_ranges(&mut self, ranges: T) where T: IntoIterator>, diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 73dc6bfd6e..c55b8b7fdf 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -151,6 +151,11 @@ impl<'a> EditorTestContext<'a> { snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end) } + pub fn set_diff_base(&mut self, diff_base: Option<&str>) { + let diff_base = diff_base.map(String::from); + self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx)); + } + /// Change the editor's text and selections using a string containing /// embedded range markers that represent the ranges and directions of /// each selection. diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 900f8967d7..e808eee24f 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -15,12 +15,12 @@ pub enum DiffHunkStatus { #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiffHunk { pub buffer_range: Range, - pub head_byte_range: Range, + pub diff_base_byte_range: Range, } impl DiffHunk { pub fn status(&self) -> DiffHunkStatus { - if self.head_byte_range.is_empty() { + if self.diff_base_byte_range.is_empty() { DiffHunkStatus::Added } else if self.buffer_range.is_empty() { DiffHunkStatus::Removed @@ -75,6 +75,7 @@ impl BufferDiff { &'a self, query_row_range: Range, buffer: &'a BufferSnapshot, + reversed: bool, ) -> impl 'a + Iterator> { let start = buffer.anchor_before(Point::new(query_row_range.start, 0)); let end = buffer.anchor_after(Point::new(query_row_range.end, 0)); @@ -86,7 +87,12 @@ impl BufferDiff { }); std::iter::from_fn(move || { - cursor.next(buffer); + if reversed { + cursor.prev(buffer); + } else { + cursor.next(buffer); + } + let hunk = cursor.item()?; let range = hunk.buffer_range.to_point(buffer); @@ -98,7 +104,7 @@ impl BufferDiff { Some(DiffHunk { buffer_range: range.start.row..end_row, - head_byte_range: hunk.head_byte_range.clone(), + diff_base_byte_range: hunk.diff_base_byte_range.clone(), }) }) } @@ -135,7 +141,7 @@ impl BufferDiff { #[cfg(test)] fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator> { - self.hunks_in_range(0..u32::MAX, text) + self.hunks_in_range(0..u32::MAX, text, false) } fn diff<'a>(head: &'a str, current: &'a str) -> Option> { @@ -171,7 +177,7 @@ impl BufferDiff { let mut first_deletion_buffer_row: Option = None; let mut buffer_row_range: Option> = None; - let mut head_byte_range: Option> = None; + let mut diff_base_byte_range: Option> = None; for line_index in 0..line_item_count { let line = patch.line_in_hunk(hunk_index, line_index).unwrap(); @@ -192,9 +198,9 @@ impl BufferDiff { if kind == GitDiffLineType::Deletion { let end = content_offset + content_len; - match &mut head_byte_range { + match &mut diff_base_byte_range { Some(head_byte_range) => head_byte_range.end = end as usize, - None => head_byte_range = Some(content_offset as usize..end as usize), + None => diff_base_byte_range = Some(content_offset as usize..end as usize), } if first_deletion_buffer_row.is_none() { @@ -215,14 +221,14 @@ impl BufferDiff { }); //unwrap_or addition without deletion - let head_byte_range = head_byte_range.unwrap_or(0..0); + let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0); let start = Point::new(buffer_row_range.start, 0); let end = Point::new(buffer_row_range.end, 0); let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end); DiffHunk { buffer_range, - head_byte_range, + diff_base_byte_range, } } } @@ -242,7 +248,7 @@ pub fn assert_hunks( .map(|hunk| { ( hunk.buffer_range.clone(), - &diff_base[hunk.head_byte_range], + &diff_base[hunk.diff_base_byte_range], buffer .text_for_range( Point::new(hunk.buffer_range.start, 0) @@ -349,7 +355,7 @@ mod tests { assert_eq!(diff.hunks(&buffer).count(), 8); assert_hunks( - diff.hunks_in_range(7..12, &buffer), + diff.hunks_in_range(7..12, &buffer, false), &buffer, &diff_base, &[ diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index e99f643e62..0756b90c36 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -681,7 +681,7 @@ impl Buffer { self.diff_base.as_deref() } - pub fn update_diff_base(&mut self, diff_base: Option, cx: &mut ModelContext) { + pub fn set_diff_base(&mut self, diff_base: Option, cx: &mut ModelContext) { self.diff_base = diff_base; self.git_diff_recalc(cx); } @@ -2294,8 +2294,10 @@ impl BufferSnapshot { pub fn git_diff_hunks_in_range<'a>( &'a self, query_row_range: Range, + reversed: bool, ) -> impl 'a + Iterator> { - self.git_diff.hunks_in_range(query_row_range, self) + self.git_diff + .hunks_in_range(query_row_range, self, reversed) } pub fn diagnostics_in_range<'a, T, O>( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f11049336b..3ad87ab7a6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4410,7 +4410,7 @@ impl Project { .await; let buffer_id = buffer.update(&mut cx, |buffer, cx| { - buffer.update_diff_base(diff_base.clone(), cx); + buffer.set_diff_base(diff_base.clone(), cx); buffer.remote_id() }); @@ -4968,7 +4968,7 @@ impl Project { .and_then(|b| b.upgrade(cx)) .ok_or_else(|| anyhow!("No such buffer {}", buffer_id))?; - buffer.update(cx, |buffer, cx| buffer.update_diff_base(diff_base, cx)); + buffer.update(cx, |buffer, cx| buffer.set_diff_base(diff_base, cx)); Ok(()) }) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9f0ebe32f7..db8fb8e3ff 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3257,7 +3257,6 @@ mod tests { } }, "c.txt": "", - })); let http_client = FakeHttpClient::with_404_response();