multi_buffer: Typed MultiBufferOffset (#42707)

This PR introduces a new `MultiBufferOffset` new type wrapping size. The
goal of this is to make it clear at the type level when we are
interacting with offsets of a multi buffer versus offsets of a language
/ text buffer. This improves readability of things quite a bit by making
it clear what kind of offsets one is working with while also reducing
accidental bugs by using the wrong kin of offset for the wrong API.

This PR also uncovered two minor bugs due to that.

Does not yet introduce the MultiBufferPoint equivalent, that is for a
follow up PR.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth
2025-11-19 22:00:58 +00:00
committed by GitHub
parent 5e21457f21
commit c98b2d6944
81 changed files with 2957 additions and 1564 deletions
+23 -16
View File
@@ -1,5 +1,5 @@
use editor::{
Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset, ToPoint,
Anchor, Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, RowExt, ToOffset, ToPoint,
display_map::{DisplayRow, DisplaySnapshot, FoldPoint, ToDisplayPoint},
movement::{
self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point,
@@ -2143,7 +2143,7 @@ pub(crate) fn sentence_backwards(
if start_of_next_sentence < start {
times = times.saturating_sub(1);
}
if times == 0 || offset == 0 {
if times == 0 || offset.0 == 0 {
return map.clip_point(
start_of_next_sentence
.to_offset(&map.buffer_snapshot())
@@ -2207,7 +2207,7 @@ pub(crate) fn sentence_forwards(
map.max_point()
}
fn next_non_blank(map: &DisplaySnapshot, start: usize) -> usize {
fn next_non_blank(map: &DisplaySnapshot, start: MultiBufferOffset) -> MultiBufferOffset {
for (c, o) in map.buffer_chars_at(start) {
if c == '\n' || !c.is_whitespace() {
return o;
@@ -2219,7 +2219,10 @@ fn next_non_blank(map: &DisplaySnapshot, start: usize) -> usize {
// given the offset after a ., !, or ? find the start of the next sentence.
// if this is not a sentence boundary, returns None.
fn start_of_next_sentence(map: &DisplaySnapshot, end_of_sentence: usize) -> Option<usize> {
fn start_of_next_sentence(
map: &DisplaySnapshot,
end_of_sentence: MultiBufferOffset,
) -> Option<MultiBufferOffset> {
let chars = map.buffer_chars_at(end_of_sentence);
let mut seen_space = false;
@@ -2253,10 +2256,10 @@ fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) -
.clip_point(Point::new((line - 1) as u32, point.column), Bias::Left),
);
let buffer_range = excerpt.buffer_range();
if offset >= buffer_range.start && offset <= buffer_range.end {
if offset >= buffer_range.start.0 && offset <= buffer_range.end.0 {
let point = map
.buffer_snapshot()
.offset_to_point(excerpt.map_offset_from_buffer(offset));
.offset_to_point(excerpt.map_offset_from_buffer(BufferOffset(offset)));
return map.clip_point(map.point_to_display_point(point, Bias::Left), Bias::Left);
}
let mut last_position = None;
@@ -2360,6 +2363,9 @@ fn matching_tag(map: &DisplaySnapshot, head: DisplayPoint) -> Option<DisplayPoin
}
fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
if !map.is_singleton() {
return display_point;
}
// https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
let display_point = map.clip_at_line_end(display_point);
let point = display_point.to_point(map);
@@ -2375,9 +2381,10 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
// Attempt to find the smallest enclosing bracket range that also contains
// the offset, which only happens if the cursor is currently in a bracket.
let range_filter = |_buffer: &language::BufferSnapshot,
opening_range: Range<usize>,
closing_range: Range<usize>| {
opening_range.contains(&offset) || closing_range.contains(&offset)
opening_range: Range<BufferOffset>,
closing_range: Range<BufferOffset>| {
opening_range.contains(&BufferOffset(offset.0))
|| closing_range.contains(&BufferOffset(offset.0))
};
let bracket_ranges = snapshot
@@ -2840,7 +2847,7 @@ fn method_motion(
for _ in 0..times {
let point = map.display_point_to_point(display_point, Bias::Left);
let offset = point.to_offset(&map.buffer_snapshot());
let offset = point.to_offset(&map.buffer_snapshot()).0;
let range = if direction == Direction::Prev {
0..offset
} else {
@@ -2869,7 +2876,7 @@ fn method_motion(
} else {
possibilities.min().unwrap_or(offset)
};
let new_point = map.clip_point(dest.to_display_point(map), Bias::Left);
let new_point = map.clip_point(MultiBufferOffset(dest).to_display_point(map), Bias::Left);
if new_point == display_point {
break;
}
@@ -2890,7 +2897,7 @@ fn comment_motion(
for _ in 0..times {
let point = map.display_point_to_point(display_point, Bias::Left);
let offset = point.to_offset(&map.buffer_snapshot());
let offset = point.to_offset(&map.buffer_snapshot()).0;
let range = if direction == Direction::Prev {
0..offset
} else {
@@ -2923,7 +2930,7 @@ fn comment_motion(
} else {
possibilities.min().unwrap_or(offset)
};
let new_point = map.clip_point(dest.to_display_point(map), Bias::Left);
let new_point = map.clip_point(MultiBufferOffset(dest).to_display_point(map), Bias::Left);
if new_point == display_point {
break;
}
@@ -2946,7 +2953,7 @@ fn section_motion(
.display_point_to_point(display_point, Bias::Left)
.to_offset(&map.buffer_snapshot());
let range = if direction == Direction::Prev {
0..offset
MultiBufferOffset(0)..offset
} else {
offset..map.buffer_snapshot().len()
};
@@ -2977,7 +2984,7 @@ fn section_motion(
let relevant = if is_start { range.start } else { range.end };
if direction == Direction::Prev && relevant < offset {
Some(relevant)
} else if direction == Direction::Next && relevant > offset + 1 {
} else if direction == Direction::Next && relevant > offset + 1usize {
Some(relevant)
} else {
None
@@ -2985,7 +2992,7 @@ fn section_motion(
});
let offset = if direction == Direction::Prev {
possibilities.max().unwrap_or(0)
possibilities.max().unwrap_or(MultiBufferOffset(0))
} else {
possibilities.min().unwrap_or(map.buffer_snapshot().len())
};