mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-30 20:24:08 +00:00
editor: Do not include inlays in word diff highlights (#49007)
Release Notes: - Fixed inlay hints being rendered as new inserted words in word based diff highlighting --------- Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
parent
21ad340f01
commit
213de2ec9b
3 changed files with 186 additions and 18 deletions
|
|
@ -105,6 +105,7 @@ use multi_buffer::{
|
|||
use project::project_settings::DiagnosticSeverity;
|
||||
use project::{InlayId, lsp_store::LspFoldingRange, lsp_store::TokenType};
|
||||
use serde::Deserialize;
|
||||
use smallvec::SmallVec;
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use text::{BufferId, LineIndent, Patch, ToOffset as _};
|
||||
use ui::{SharedString, px};
|
||||
|
|
@ -1694,6 +1695,38 @@ impl DisplaySnapshot {
|
|||
DisplayPoint(block_point)
|
||||
}
|
||||
|
||||
/// Converts a buffer offset range into one or more `DisplayPoint` ranges
|
||||
/// that cover only actual buffer text, excluding any inlay hint text that
|
||||
/// falls within the range.
|
||||
pub fn isomorphic_display_point_ranges_for_buffer_range(
|
||||
&self,
|
||||
range: Range<MultiBufferOffset>,
|
||||
) -> SmallVec<[Range<DisplayPoint>; 1]> {
|
||||
let inlay_snapshot = self.inlay_snapshot();
|
||||
inlay_snapshot
|
||||
.buffer_offset_to_inlay_ranges(range)
|
||||
.map(|inlay_range| {
|
||||
let inlay_point_to_display_point = |inlay_point: InlayPoint, bias: Bias| {
|
||||
let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
|
||||
let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
|
||||
let block_point = self.block_snapshot.to_block_point(wrap_point);
|
||||
DisplayPoint(block_point)
|
||||
};
|
||||
|
||||
let start = inlay_point_to_display_point(
|
||||
inlay_snapshot.to_point(inlay_range.start),
|
||||
Bias::Left,
|
||||
);
|
||||
let end = inlay_point_to_display_point(
|
||||
inlay_snapshot.to_point(inlay_range.end),
|
||||
Bias::Left,
|
||||
);
|
||||
start..end
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
|
||||
self.inlay_snapshot()
|
||||
.to_buffer_point(self.display_point_to_inlay_point(point, bias))
|
||||
|
|
@ -3956,4 +3989,88 @@ pub mod tests {
|
|||
store.update_user_settings(cx, f);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_isomorphic_display_point_ranges_for_buffer_range(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local("let x = 5;\n", cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
|
||||
let font_size = px(14.0);
|
||||
let map = cx.new(|cx| {
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
font("Helvetica"),
|
||||
font_size,
|
||||
None,
|
||||
1,
|
||||
1,
|
||||
FoldPlaceholder::test(),
|
||||
DiagnosticSeverity::Warning,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Without inlays, a buffer range maps to a single display range.
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
|
||||
MultiBufferOffset(4)..MultiBufferOffset(9),
|
||||
);
|
||||
assert_eq!(ranges.len(), 1);
|
||||
// "x = 5" is columns 4..9 with no inlays shifting anything.
|
||||
assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
|
||||
assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 9));
|
||||
|
||||
// Insert a 4-char inlay hint ": i32" at buffer offset 5 (after "x").
|
||||
map.update(cx, |map, cx| {
|
||||
map.splice_inlays(
|
||||
&[],
|
||||
vec![Inlay::mock_hint(
|
||||
0,
|
||||
buffer_snapshot.anchor_after(MultiBufferOffset(5)),
|
||||
": i32",
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(snapshot.text(), "let x: i32 = 5;\n");
|
||||
|
||||
// A buffer range [4..9] ("x = 5") now spans across the inlay.
|
||||
// It should be split into two display ranges that skip the inlay text.
|
||||
let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
|
||||
MultiBufferOffset(4)..MultiBufferOffset(9),
|
||||
);
|
||||
assert_eq!(
|
||||
ranges.len(),
|
||||
2,
|
||||
"expected the range to be split around the inlay, got: {:?}",
|
||||
ranges,
|
||||
);
|
||||
// First sub-range: buffer [4, 5) → "x" at display columns 4..5
|
||||
assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 4));
|
||||
assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
|
||||
// Second sub-range: buffer [5, 9) → " = 5" at display columns 10..14
|
||||
// (shifted right by the 5-char ": i32" inlay)
|
||||
assert_eq!(ranges[1].start, DisplayPoint::new(DisplayRow(0), 10));
|
||||
assert_eq!(ranges[1].end, DisplayPoint::new(DisplayRow(0), 14));
|
||||
|
||||
// A range entirely before the inlay is not split.
|
||||
let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
|
||||
MultiBufferOffset(0)..MultiBufferOffset(5),
|
||||
);
|
||||
assert_eq!(ranges.len(), 1);
|
||||
assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 0));
|
||||
assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 5));
|
||||
|
||||
// A range entirely after the inlay is not split.
|
||||
let ranges = snapshot.isomorphic_display_point_ranges_for_buffer_range(
|
||||
MultiBufferOffset(5)..MultiBufferOffset(9),
|
||||
);
|
||||
assert_eq!(ranges.len(), 1);
|
||||
assert_eq!(ranges[0].start, DisplayPoint::new(DisplayRow(0), 10));
|
||||
assert_eq!(ranges[0].end, DisplayPoint::new(DisplayRow(0), 14));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -938,6 +938,51 @@ impl InlaySnapshot {
|
|||
self.inlay_point_cursor().map(point)
|
||||
}
|
||||
|
||||
/// Converts a buffer offset range into one or more `InlayOffset` ranges that
|
||||
/// cover only the actual buffer text, skipping any inlay hint text that falls
|
||||
/// within the range. When there are no inlays the returned vec contains a
|
||||
/// single element identical to the input mapped into inlay-offset space.
|
||||
pub fn buffer_offset_to_inlay_ranges(
|
||||
&self,
|
||||
range: Range<MultiBufferOffset>,
|
||||
) -> impl Iterator<Item = Range<InlayOffset>> {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
|
||||
cursor.seek(&range.start, Bias::Right);
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
loop {
|
||||
match cursor.item()? {
|
||||
Transform::Isomorphic(_) => {
|
||||
let seg_buffer_start = cursor.start().0;
|
||||
let seg_buffer_end = cursor.end().0;
|
||||
let seg_inlay_start = cursor.start().1;
|
||||
|
||||
let overlap_start = cmp::max(range.start, seg_buffer_start);
|
||||
let overlap_end = cmp::min(range.end, seg_buffer_end);
|
||||
|
||||
let past_end = seg_buffer_end >= range.end;
|
||||
cursor.next();
|
||||
|
||||
if overlap_start < overlap_end {
|
||||
let inlay_start =
|
||||
InlayOffset(seg_inlay_start.0 + (overlap_start - seg_buffer_start));
|
||||
let inlay_end =
|
||||
InlayOffset(seg_inlay_start.0 + (overlap_end - seg_buffer_start));
|
||||
return Some(inlay_start..inlay_end);
|
||||
}
|
||||
|
||||
if past_end {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Transform::Inlay(_) => cursor.next(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[ztracing::instrument(skip_all)]
|
||||
pub fn inlay_point_cursor(&self) -> InlayPointCursor<'_> {
|
||||
let cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
|
||||
|
|
|
|||
|
|
@ -5504,25 +5504,31 @@ impl EditorElement {
|
|||
})
|
||||
.filter(|(_, status)| status.is_modified())
|
||||
.flat_map(|(word_diffs, _)| word_diffs)
|
||||
.filter_map(|word_diff| {
|
||||
let start_point = word_diff.start.to_display_point(&snapshot.display_snapshot);
|
||||
let end_point = word_diff.end.to_display_point(&snapshot.display_snapshot);
|
||||
let start_row_offset = start_point.row().0.saturating_sub(start_row.0) as usize;
|
||||
.flat_map(|word_diff| {
|
||||
let display_ranges = snapshot
|
||||
.display_snapshot
|
||||
.isomorphic_display_point_ranges_for_buffer_range(
|
||||
word_diff.start..word_diff.end,
|
||||
);
|
||||
|
||||
row_infos
|
||||
.get(start_row_offset)
|
||||
.and_then(|row_info| row_info.diff_status)
|
||||
.and_then(|diff_status| {
|
||||
let background_color = match diff_status.kind {
|
||||
DiffHunkStatusKind::Added => colors.version_control_word_added,
|
||||
DiffHunkStatusKind::Deleted => colors.version_control_word_deleted,
|
||||
DiffHunkStatusKind::Modified => {
|
||||
debug_panic!("modified diff status for row info");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some((start_point..end_point, background_color))
|
||||
})
|
||||
display_ranges.into_iter().filter_map(|range| {
|
||||
let start_row_offset = range.start.row().0.saturating_sub(start_row.0) as usize;
|
||||
|
||||
let diff_status = row_infos
|
||||
.get(start_row_offset)
|
||||
.and_then(|row_info| row_info.diff_status)?;
|
||||
|
||||
let background_color = match diff_status.kind {
|
||||
DiffHunkStatusKind::Added => colors.version_control_word_added,
|
||||
DiffHunkStatusKind::Deleted => colors.version_control_word_deleted,
|
||||
DiffHunkStatusKind::Modified => {
|
||||
debug_panic!("modified diff status for row info");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((range, background_color))
|
||||
})
|
||||
});
|
||||
|
||||
highlighted_ranges.extend(word_highlights);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue