Fix gutter highlights not matching diff hunks near excerpt boundaries (#25600)
Release Notes: - Fixed gutter highlights not matching diff hunks in multibuffers in some cases --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
d68d858a10
commit
198f56c763
4 changed files with 173 additions and 66 deletions
|
@ -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<usize>,
|
||||
display_row_range: Range<DisplayRow>,
|
||||
multi_buffer_range: Range<Anchor>,
|
||||
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<DisplayRow>,
|
||||
folded_buffers: &'a HashSet<BufferId>,
|
||||
) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
|
||||
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<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
|
||||
self.display_snapshot.buffer_snapshot.language_at(position)
|
||||
}
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
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, |_| {});
|
||||
|
|
|
@ -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<usize>,
|
||||
display_row_range: Range<DisplayRow>,
|
||||
multi_buffer_range: Range<Anchor>,
|
||||
status: DiffHunkStatus,
|
||||
},
|
||||
}
|
||||
|
||||
struct SelectionLayout {
|
||||
head: DisplayPoint,
|
||||
cursor_shape: CursorShape,
|
||||
|
@ -1553,50 +1540,11 @@ impl EditorElement {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
|
||||
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<Hitbox>)>::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::<Vec<_>>();
|
||||
let git_gutter_setting = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.git_gutter
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue