multi_buffer: Make anchor_in_excerpt fallible for bad text anchors (#40496)

`MultiBuffer::anchor_in_excerpt` currently just wraps the given text
anchor in a multibuffer anchor. This allows one to get a multibuffer
anchor that points outside its excerpt which is basically never what one
wants. This PR now does a bounds check and returns `None` if the given
text anchor is not within the bounds of the excerpt.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Lukas Wirth
2025-10-17 15:40:37 +00:00
committed by GitHub
co-authored by Kirill Bulatov
parent 7f9898a90b
commit 83bfe2ff7b
13 changed files with 142 additions and 194 deletions
+6 -12
View File
@@ -5989,15 +5989,8 @@ impl Editor {
let snapshot = self.buffer.read(cx).snapshot(cx);
let newest_anchor = self.selections.newest_anchor();
let replace_range_multibuffer = {
let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
let multibuffer_anchor = snapshot
.anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
.unwrap()
..snapshot
.anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
.unwrap();
multibuffer_anchor.start.to_offset(&snapshot)
..multibuffer_anchor.end.to_offset(&snapshot)
let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
excerpt.map_range_from_buffer(replace_range.clone())
};
if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
return None;
@@ -7981,9 +7974,10 @@ impl Editor {
let edits = edits
.into_iter()
.flat_map(|(range, new_text)| {
let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
Some((start..end, new_text))
Some((
multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
new_text,
))
})
.collect::<Vec<_>>();
if edits.is_empty() {
+2 -2
View File
@@ -7464,8 +7464,8 @@ impl EditorElement {
}
let clipped_start = range.start.max(&buffer_range.start, buffer);
let clipped_end = range.end.min(&buffer_range.end, buffer);
let range = buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_start)?
..buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_end)?;
let range = buffer_snapshot
.anchor_range_in_excerpt(excerpt_id, clipped_start..clipped_end)?;
let start = range.start.to_display_point(display_snapshot);
let end = range.end.to_display_point(display_snapshot);
let selection_layout = SelectionLayout {
+9 -13
View File
@@ -534,10 +534,9 @@ pub fn show_link_definition(
if let Some((url_range, url)) = find_url(&buffer, text_anchor, cx.clone()) {
this.read_with(cx, |_, _| {
let range = maybe!({
let start =
snapshot.anchor_in_excerpt(excerpt_id, url_range.start)?;
let end = snapshot.anchor_in_excerpt(excerpt_id, url_range.end)?;
Some(RangeInEditor::Text(start..end))
let range =
snapshot.anchor_range_in_excerpt(excerpt_id, url_range)?;
Some(RangeInEditor::Text(range))
});
(range, vec![HoverLink::Url(url)])
})
@@ -546,10 +545,9 @@ pub fn show_link_definition(
find_file(&buffer, project.clone(), text_anchor, cx).await
{
let range = maybe!({
let start =
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
let end = snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
Some(RangeInEditor::Text(start..end))
let range =
snapshot.anchor_range_in_excerpt(excerpt_id, filename_range)?;
Some(RangeInEditor::Text(range))
});
Some((range, vec![HoverLink::File(filename)]))
@@ -562,13 +560,11 @@ pub fn show_link_definition(
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
let range = snapshot.anchor_range_in_excerpt(
excerpt_id,
origin.range.start,
origin.range.clone(),
)?;
let end = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
Some(RangeInEditor::Text(start..end))
Some(RangeInEditor::Text(range))
})
}),
definition_result.into_iter().map(HoverLink::Text).collect(),
+3 -6
View File
@@ -467,13 +467,10 @@ fn show_hover(
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
let range = snapshot
.buffer_snapshot()
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot()
.anchor_in_excerpt(excerpt_id, range.end)?;
Some(start..end)
.anchor_range_in_excerpt(excerpt_id, range)?;
Some(range)
})
.or_else(|| {
let snapshot = &snapshot.buffer_snapshot();
+1 -2
View File
@@ -364,10 +364,9 @@ impl FollowableItem for Editor {
) {
let buffer = self.buffer.read(cx);
let buffer = buffer.read(cx);
let Some((excerpt_id, _, _)) = buffer.as_singleton() else {
let Some(position) = buffer.as_singleton_anchor(location) else {
return;
};
let position = buffer.anchor_in_excerpt(*excerpt_id, location).unwrap();
let selection = Selection {
id: 0,
reversed: false,
+13 -26
View File
@@ -251,25 +251,14 @@ impl Editor {
{
continue;
}
let Some(color_start_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_before(
buffer_snapshot
.clip_point_utf16(color_start, Bias::Left),
),
)
else {
continue;
};
let Some(color_end_anchor) = multi_buffer_snapshot
.anchor_in_excerpt(
*excerpt_id,
buffer_snapshot.anchor_after(
buffer_snapshot
.clip_point_utf16(color_end, Bias::Right),
),
)
let start = buffer_snapshot.anchor_before(
buffer_snapshot.clip_point_utf16(color_start, Bias::Left),
);
let end = buffer_snapshot.anchor_after(
buffer_snapshot.clip_point_utf16(color_end, Bias::Right),
);
let Some(range) = multi_buffer_snapshot
.anchor_range_in_excerpt(*excerpt_id, start..end)
else {
continue;
};
@@ -285,16 +274,14 @@ impl Editor {
new_buffer_colors.binary_search_by(|(probe, _)| {
probe
.start
.cmp(&color_start_anchor, &multi_buffer_snapshot)
.cmp(&range.start, &multi_buffer_snapshot)
.then_with(|| {
probe.end.cmp(
&color_end_anchor,
&multi_buffer_snapshot,
)
probe
.end
.cmp(&range.end, &multi_buffer_snapshot)
})
});
new_buffer_colors
.insert(i, (color_start_anchor..color_end_anchor, color));
new_buffer_colors.insert(i, (range, color));
break;
}
}