From 83bfe2ff7ba9b711bb80f80a4aad15f585ff964f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 17 Oct 2025 17:40:37 +0200 Subject: [PATCH] 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 --- crates/agent_ui/src/acp/message_editor.rs | 27 +++---- crates/agent_ui/src/inline_assistant.rs | 7 +- crates/agent_ui/src/text_thread_editor.rs | 55 +++++--------- crates/editor/src/editor.rs | 18 ++--- crates/editor/src/element.rs | 4 +- crates/editor/src/hover_links.rs | 22 +++--- crates/editor/src/hover_popover.rs | 9 +-- crates/editor/src/items.rs | 3 +- crates/editor/src/lsp_colors.rs | 39 ++++------ crates/git_ui/src/conflict_view.rs | 72 ++++++------------- crates/language_tools/src/syntax_tree_view.rs | 7 +- crates/multi_buffer/src/multi_buffer.rs | 63 ++++++++++++---- crates/outline_panel/src/outline_panel.rs | 14 ++-- 13 files changed, 144 insertions(+), 196 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 4be30705e00ecbf18cc1e65bab3dc9dab8e053d9..be1c205cee661d401d577b0bcb2d50dc62b4e38c 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -292,15 +292,10 @@ impl MessageEditor { let snapshot = self .editor .update(cx, |editor, cx| editor.snapshot(window, cx)); - let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot().as_singleton() else { - return Task::ready(()); - }; - let Some(start_anchor) = snapshot - .buffer_snapshot() - .anchor_in_excerpt(*excerpt_id, start) - else { + let Some(start_anchor) = snapshot.buffer_snapshot().as_singleton_anchor(start) else { return Task::ready(()); }; + let excerpt_id = start_anchor.excerpt_id; let end_anchor = snapshot .buffer_snapshot() .anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1); @@ -332,7 +327,7 @@ impl MessageEditor { }) .shared(); insert_crease_for_mention( - *excerpt_id, + excerpt_id, start, content_len, mention_uri.name().into(), @@ -344,7 +339,7 @@ impl MessageEditor { ) } else { insert_crease_for_mention( - *excerpt_id, + excerpt_id, start, content_len, crease_text, @@ -546,10 +541,7 @@ impl MessageEditor { cx: &mut Context, ) { let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx); - let Some((&excerpt_id, _, _)) = snapshot.as_singleton() else { - return; - }; - let Some(start) = snapshot.anchor_in_excerpt(excerpt_id, source_range.start) else { + let Some(start) = snapshot.as_singleton_anchor(source_range.start) else { return; }; @@ -1694,13 +1686,10 @@ mod tests { editor.update_in(cx, |editor, window, cx| { let snapshot = editor.buffer().read(cx).snapshot(cx); - let start = snapshot - .anchor_in_excerpt(excerpt_id, completion.replace_range.start) - .unwrap(); - let end = snapshot - .anchor_in_excerpt(excerpt_id, completion.replace_range.end) + let range = snapshot + .anchor_range_in_excerpt(excerpt_id, completion.replace_range) .unwrap(); - editor.edit([(start..end, completion.new_text)], cx); + editor.edit([(range, completion.new_text)], cx); (completion.confirm.unwrap())(CompletionIntent::Complete, window, cx); }); diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index d24dc4ab781585e9ebabc7f19016e4da2457a873..3d25e614ad69d264700476d52ddc0407590b9e9c 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -1878,12 +1878,7 @@ impl CodeActionProvider for AssistantCodeActionProvider { } let multibuffer_snapshot = multibuffer.read(cx); - Some( - multibuffer_snapshot - .anchor_in_excerpt(excerpt_id, action.range.start)? - ..multibuffer_snapshot - .anchor_in_excerpt(excerpt_id, action.range.end)?, - ) + multibuffer_snapshot.anchor_range_in_excerpt(excerpt_id, action.range) }) })? .context("invalid range")?; diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 5e4ecf3db171e0fb7b9d465482e2858e04dbe0d1..1b19ef1aa4046b1159d849e56a9e959d13dc53ce 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -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::(), 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) - .unwrap(); - let end = buffer - .anchor_in_excerpt(excerpt_id, section.range.end) + let range = buffer + .anchor_range_in_excerpt(excerpt_id, section.range) .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(), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6229af77ebb27c4d163a26105517905c5665fe04..15af61f5d28336f77976ee3fadc783016cc283bd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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::>(); if edits.is_empty() { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f93d5eac2e11a57b9674bac408c667087ea02add..290c8892d9c93ee805ee8106bab183921f616099 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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 { diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index d51617b6371abc7df5834242d9632a5abcaac6cb..4a1a6a934678adb9512ee6883684d2ecb1b2d90a 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -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(), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 863ce297be9149f62bb7e880658e8a084968fb81..2b0ebf52805493d008c8b69ec4ff86991d6c743d 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -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(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f56e7dbaf87fa05e1423f639c7473259c8fc956c..de47f66183ee972e77a54e436bf25b94cef66e36 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -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, diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index e7fdb6aaa1f661b717945b568f23062a2fb41f12..e99cab2aa938614be5478bdf17ef78b1f626a6f2 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -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; } } diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index ee1b82920d7621f6e5b1d4ab9a9b44e151fbf82a..91cc3ce76b3f10aa310185b566b6c6086580b69c 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -234,11 +234,7 @@ fn conflicts_updated( continue; }; let excerpt_id = *excerpt_id; - let Some(range) = snapshot - .anchor_in_excerpt(excerpt_id, conflict_range.start) - .zip(snapshot.anchor_in_excerpt(excerpt_id, conflict_range.end)) - .map(|(start, end)| start..end) - else { + let Some(range) = snapshot.anchor_range_in_excerpt(excerpt_id, conflict_range) else { continue; }; removed_highlighted_ranges.push(range.clone()); @@ -321,27 +317,12 @@ fn update_conflict_highlighting( buffer: &editor::MultiBufferSnapshot, excerpt_id: editor::ExcerptId, cx: &mut Context, -) { +) -> Option<()> { log::debug!("update conflict highlighting for {conflict:?}"); - let outer_start = buffer - .anchor_in_excerpt(excerpt_id, conflict.range.start) - .unwrap(); - let outer_end = buffer - .anchor_in_excerpt(excerpt_id, conflict.range.end) - .unwrap(); - let our_start = buffer - .anchor_in_excerpt(excerpt_id, conflict.ours.start) - .unwrap(); - let our_end = buffer - .anchor_in_excerpt(excerpt_id, conflict.ours.end) - .unwrap(); - let their_start = buffer - .anchor_in_excerpt(excerpt_id, conflict.theirs.start) - .unwrap(); - let their_end = buffer - .anchor_in_excerpt(excerpt_id, conflict.theirs.end) - .unwrap(); + let outer = buffer.anchor_range_in_excerpt(excerpt_id, conflict.range.clone())?; + let ours = buffer.anchor_range_in_excerpt(excerpt_id, conflict.ours.clone())?; + let theirs = buffer.anchor_range_in_excerpt(excerpt_id, conflict.theirs.clone())?; let ours_background = cx.theme().colors().version_control_conflict_marker_ours; let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs; @@ -352,32 +333,29 @@ fn update_conflict_highlighting( }; editor.insert_gutter_highlight::( - outer_start..their_end, + outer.start..theirs.end, |cx| cx.theme().colors().editor_background, cx, ); // Prevent diff hunk highlighting within the entire conflict region. - editor.highlight_rows::(outer_start..outer_end, theirs_background, options, cx); - editor.highlight_rows::(our_start..our_end, ours_background, options, cx); + editor.highlight_rows::(outer.clone(), theirs_background, options, cx); + editor.highlight_rows::(ours.clone(), ours_background, options, cx); editor.highlight_rows::( - outer_start..our_start, + outer.start..ours.start, ours_background, options, cx, ); - editor.highlight_rows::( - their_start..their_end, - theirs_background, - options, - cx, - ); + editor.highlight_rows::(theirs.clone(), theirs_background, options, cx); editor.highlight_rows::( - their_end..outer_end, + theirs.end..outer.end, theirs_background, options, cx, ); + + Some(()) } fn render_conflict_buttons( @@ -488,20 +466,16 @@ pub(crate) fn resolve_conflict( }) .ok()?; let &(_, block_id) = &state.block_ids[ix]; - let start = snapshot - .anchor_in_excerpt(excerpt_id, resolved_conflict.range.start) - .unwrap(); - let end = snapshot - .anchor_in_excerpt(excerpt_id, resolved_conflict.range.end) - .unwrap(); - - editor.remove_gutter_highlights::(vec![start..end], cx); - - editor.remove_highlighted_rows::(vec![start..end], cx); - editor.remove_highlighted_rows::(vec![start..end], cx); - editor.remove_highlighted_rows::(vec![start..end], cx); - editor.remove_highlighted_rows::(vec![start..end], cx); - editor.remove_highlighted_rows::(vec![start..end], cx); + let range = + snapshot.anchor_range_in_excerpt(excerpt_id, resolved_conflict.range)?; + + editor.remove_gutter_highlights::(vec![range.clone()], cx); + + editor.remove_highlighted_rows::(vec![range.clone()], cx); + editor.remove_highlighted_rows::(vec![range.clone()], cx); + editor.remove_highlighted_rows::(vec![range.clone()], cx); + editor.remove_highlighted_rows::(vec![range.clone()], cx); + editor.remove_highlighted_rows::(vec![range], cx); editor.remove_blocks(HashSet::from_iter([block_id]), None, cx); Some((workspace, project, multibuffer, buffer)) }) diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index eea8c8a43b9b0dad854cc2a24878123ffeed6837..c1c4b604269ae8d731e39b541e05fbed139692cc 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -356,12 +356,7 @@ impl SyntaxTreeView { let multibuffer = editor_state.editor.read(cx).buffer(); let multibuffer = multibuffer.read(cx).snapshot(cx); let excerpt_id = buffer_state.excerpt_id; - let range = multibuffer - .anchor_in_excerpt(excerpt_id, range.start) - .unwrap() - ..multibuffer - .anchor_in_excerpt(excerpt_id, range.end) - .unwrap(); + let range = multibuffer.anchor_range_in_excerpt(excerpt_id, range)?; // Update the editor with the anchor range. editor_state.editor.update(cx, |editor, cx| { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 0347757660c584efbfe3c786964d1cad51d4a325..ac769dfdafcf79af51c1f3119453e89d39ca333a 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5230,8 +5230,30 @@ impl MultiBufferSnapshot { } } + /// Wraps the [`text::Anchor`] in a [`multi_buffer::Anchor`] if this multi-buffer is a singleton. + pub fn as_singleton_anchor(&self, text_anchor: text::Anchor) -> Option { + let (excerpt, buffer, _) = self.as_singleton()?; + Some(Anchor::in_buffer(*excerpt, buffer, text_anchor)) + } + /// Returns an anchor for the given excerpt and text anchor, - /// returns None if the excerpt_id is no longer valid. + /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds. + pub fn anchor_range_in_excerpt( + &self, + excerpt_id: ExcerptId, + text_anchor: Range, + ) -> Option> { + let excerpt_id = self.latest_excerpt_id(excerpt_id); + let excerpt = self.excerpt(excerpt_id)?; + + Some( + self.anchor_in_excerpt_(excerpt, text_anchor.start)? + ..self.anchor_in_excerpt_(excerpt, text_anchor.end)?, + ) + } + + /// Returns an anchor for the given excerpt and text anchor, + /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds. pub fn anchor_in_excerpt( &self, excerpt_id: ExcerptId, @@ -5239,8 +5261,32 @@ impl MultiBufferSnapshot { ) -> Option { let excerpt_id = self.latest_excerpt_id(excerpt_id); let excerpt = self.excerpt(excerpt_id)?; + self.anchor_in_excerpt_(excerpt, text_anchor) + } + + fn anchor_in_excerpt_(&self, excerpt: &Excerpt, text_anchor: text::Anchor) -> Option { + match text_anchor.buffer_id { + Some(buffer_id) if buffer_id == excerpt.buffer_id => (), + Some(_) => return None, + None if text_anchor == text::Anchor::MAX || text_anchor == text::Anchor::MIN => { + return Some(Anchor::in_buffer( + excerpt.id, + excerpt.buffer_id, + text_anchor, + )); + } + None => return None, + } + + let context = &excerpt.range.context; + if context.start.cmp(&text_anchor, &excerpt.buffer).is_gt() + || context.end.cmp(&text_anchor, &excerpt.buffer).is_lt() + { + return None; + } + Some(Anchor::in_buffer( - excerpt_id, + excerpt.id, excerpt.buffer_id, text_anchor, )) @@ -6075,22 +6121,15 @@ impl MultiBufferSnapshot { .flat_map(|item| { Some(OutlineItem { depth: item.depth, - range: self.anchor_in_excerpt(*excerpt_id, item.range.start)? - ..self.anchor_in_excerpt(*excerpt_id, item.range.end)?, + range: self.anchor_range_in_excerpt(*excerpt_id, item.range)?, text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, body_range: item.body_range.and_then(|body_range| { - Some( - self.anchor_in_excerpt(*excerpt_id, body_range.start)? - ..self.anchor_in_excerpt(*excerpt_id, body_range.end)?, - ) + self.anchor_range_in_excerpt(*excerpt_id, body_range) }), annotation_range: item.annotation_range.and_then(|annotation_range| { - Some( - self.anchor_in_excerpt(*excerpt_id, annotation_range.start)? - ..self.anchor_in_excerpt(*excerpt_id, annotation_range.end)?, - ) + self.anchor_range_in_excerpt(*excerpt_id, annotation_range) }), }) }) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 6dcc572e6c022112f886b9c192a65064040cf1af..847edef1c697e8e008ec8f1010e99fb87362284e 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -3192,13 +3192,13 @@ impl OutlinePanel { .into_iter() .flat_map(|excerpt| excerpt.iter_outlines()) .flat_map(|outline| { - let start = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, outline.range.start)? - .to_display_point(&editor_snapshot); - let end = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, outline.range.end)? - .to_display_point(&editor_snapshot); - Some((start..end, outline)) + let range = multi_buffer_snapshot + .anchor_range_in_excerpt(excerpt_id, outline.range.clone())?; + Some(( + range.start.to_display_point(&editor_snapshot) + ..range.end.to_display_point(&editor_snapshot), + outline, + )) }) .collect::>();