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
+19 -36
View File
@@ -602,10 +602,8 @@ impl TextThreadEditor {
if let Some((crease_id, start)) = self.pending_thought_process.take() {
self.editor.update(cx, |editor, cx| {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let (excerpt_id, _, _) = multi_buffer_snapshot.as_singleton().unwrap();
let start_anchor = multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, start)
.unwrap();
let start_anchor =
multi_buffer_snapshot.as_singleton_anchor(start).unwrap();
editor.display_map.update(cx, |display_map, cx| {
display_map.unfold_intersecting(
@@ -696,13 +694,10 @@ impl TextThreadEditor {
}
};
let start = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.start)
let range = buffer
.anchor_range_in_excerpt(excerpt_id, command.source_range.clone())
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.end)
.unwrap();
Crease::inline(start..end, placeholder, render_toggle, render_trailer)
Crease::inline(range, placeholder, render_toggle, render_trailer)
}),
cx,
);
@@ -773,14 +768,11 @@ impl TextThreadEditor {
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let start = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
editor.remove_folds_with_type(
&[start..end],
&[range],
TypeId::of::<PendingSlashCommand>(),
false,
cx,
@@ -797,14 +789,11 @@ impl TextThreadEditor {
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let crease_start = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
.unwrap();
let crease_end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
let crease = Crease::inline(
crease_start..crease_end,
range,
invoked_slash_command_fold_placeholder(command_id, context),
fold_toggle("invoked-slash-command"),
|_row, _folded, _window, _cx| Empty.into_any(),
@@ -842,17 +831,14 @@ impl TextThreadEditor {
let mut buffer_rows_to_fold = BTreeSet::new();
let mut creases = Vec::new();
for (section, status) in sections {
let start = buffer
.anchor_in_excerpt(excerpt_id, section.range.start)
let range = buffer
.anchor_range_in_excerpt(excerpt_id, section.range)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, section.range.end)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
creases.push(
Crease::inline(
start..end,
range,
FoldPlaceholder {
render: render_thought_process_fold_icon_button(
cx.entity().downgrade(),
@@ -894,17 +880,14 @@ impl TextThreadEditor {
let mut buffer_rows_to_fold = BTreeSet::new();
let mut creases = Vec::new();
for section in sections {
let start = buffer
.anchor_in_excerpt(excerpt_id, section.range.start)
let range = buffer
.anchor_range_in_excerpt(excerpt_id, section.range)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, section.range.end)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
creases.push(
Crease::inline(
start..end,
range,
FoldPlaceholder {
render: render_fold_icon_button(
cx.entity().downgrade(),