Make anchor_in_excerpt Optional (#8975)

We were seeing panics due to callers assuming they had valid
excerpt_ids, but that cannot easily be guaranteed across await points as
anyone may remove an excerpt.

Release Notes:

- Fixed a panic when hovering in a multibuffer
This commit is contained in:
Conrad Irwin 2024-03-06 18:55:36 -07:00 committed by GitHub
parent ae5ec9224c
commit 0b87be71e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 101 additions and 73 deletions

View file

@ -2413,7 +2413,9 @@ impl ConversationEditor {
.read(cx)
.messages(cx)
.map(|message| BlockProperties {
position: buffer.anchor_in_excerpt(excerpt_id, message.anchor),
position: buffer
.anchor_in_excerpt(excerpt_id, message.anchor)
.unwrap(),
height: 2,
style: BlockStyle::Sticky,
render: Arc::new({

View file

@ -517,15 +517,15 @@ impl ProjectDiagnosticsEditor {
self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().map(|block| {
blocks_to_add.into_iter().flat_map(|block| {
let (excerpt_id, text_anchor) = block.position;
BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
Some(BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
height: block.height,
style: block.style,
render: block.render,
disposition: block.disposition,
}
})
}),
Some(Autoscroll::fit()),
cx,
@ -589,14 +589,16 @@ impl ProjectDiagnosticsEditor {
Ok(ix) | Err(ix) => ix,
};
if let Some(group) = groups.get(group_ix) {
let offset = excerpts_snapshot
if let Some(offset) = excerpts_snapshot
.anchor_in_excerpt(
group.excerpts[group.primary_excerpt_ix],
group.primary_diagnostic.range.start,
)
.to_offset(&excerpts_snapshot);
selection.start = offset;
selection.end = offset;
.map(|anchor| anchor.to_offset(&excerpts_snapshot))
{
selection.start = offset;
selection.end = offset;
}
}
}
}

View file

@ -13,7 +13,7 @@ use project::{
};
use std::ops::Range;
use theme::ActiveTheme as _;
use util::TryFutureExt;
use util::{maybe, TryFutureExt};
#[derive(Debug)]
pub struct HoveredLinkState {
@ -424,12 +424,13 @@ pub fn show_link_definition(
TriggerPoint::Text(_) => {
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) {
this.update(&mut cx, |_, _| {
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)),
vec![HoverLink::Url(url)],
)
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))
});
(range, vec![HoverLink::Url(url)])
})
.ok()
} else if let Some(project) = project {
@ -449,12 +450,14 @@ pub fn show_link_definition(
.map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().map(|origin| {
let start = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.start);
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.end);
RangeInEditor::Text(start..end)
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result.into_iter().map(HoverLink::Text).collect(),

View file

@ -294,18 +294,19 @@ fn show_hover(
let hover_popover = match hover_result {
Some(hover_result) if !hover_result.is_empty() => {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = if let Some(range) = hover_result.range {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start);
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end);
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
start..end
} else {
anchor..anchor
};
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
let language_registry =
project.update(&mut cx, |p, _| p.languages().clone())?;

View file

@ -460,14 +460,15 @@ impl InlayHintCache {
if !old_kinds.contains(&cached_hint.kind)
&& new_kinds.contains(&cached_hint.kind)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
multi_buffer_snapshot.anchor_in_excerpt(
*excerpt_id,
cached_hint.position,
),
&cached_hint,
));
if let Some(anchor) = multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, cached_hint.position)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
anchor,
&cached_hint,
));
}
}
excerpt_cache.next();
}
@ -483,12 +484,15 @@ impl InlayHintCache {
let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
let cached_hint_kind = maybe_missed_cached_hint.kind;
if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
to_insert.push(Inlay::hint(
cached_hint_id.id(),
multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
&maybe_missed_cached_hint,
));
if let Some(anchor) = multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
anchor,
&maybe_missed_cached_hint,
));
}
}
}
}
@ -1200,11 +1204,13 @@ fn apply_hint_update(
.allowed_hint_kinds
.contains(&new_hint.kind)
{
let new_hint_position =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
if let Some(new_hint_position) =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
{
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);

View file

@ -1154,8 +1154,8 @@ impl SearchableItem for Editor {
let end = excerpt
.buffer
.anchor_before(excerpt_range.start + range.end);
buffer.anchor_in_excerpt(excerpt.id, start)
..buffer.anchor_in_excerpt(excerpt.id, end)
buffer.anchor_in_excerpt(excerpt.id, start).unwrap()
..buffer.anchor_in_excerpt(excerpt.id, end).unwrap()
}),
);
}

View file

@ -227,8 +227,12 @@ 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)
..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
let range = multibuffer
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap()
..multibuffer
.anchor_in_excerpt(excerpt_id, range.end)
.unwrap();
// Update the editor with the anchor range.
editor_state.editor.update(cx, |editor, cx| {

View file

@ -2788,7 +2788,13 @@ impl MultiBufferSnapshot {
}
}
pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
/// Returns an anchor for the given excerpt and text anchor,
/// returns None if the excerpt_id is no longer valid.
pub fn anchor_in_excerpt(
&self,
excerpt_id: ExcerptId,
text_anchor: text::Anchor,
) -> Option<Anchor> {
let locator = self.excerpt_locator_for_id(excerpt_id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
cursor.seek(locator, Bias::Left, &());
@ -2796,14 +2802,14 @@ impl MultiBufferSnapshot {
if excerpt.id == excerpt_id {
let text_anchor = excerpt.clip_anchor(text_anchor);
drop(cursor);
return Anchor {
return Some(Anchor {
buffer_id: Some(excerpt.buffer_id),
excerpt_id,
text_anchor,
};
});
}
}
panic!("excerpt not found");
None
}
pub fn can_resolve(&self, anchor: &Anchor) -> bool {
@ -3283,13 +3289,15 @@ impl MultiBufferSnapshot {
outline
.items
.into_iter()
.map(|item| OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(*excerpt_id, item.range.start)
..self.anchor_in_excerpt(*excerpt_id, item.range.end),
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
.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)?,
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
})
})
.collect(),
))
@ -3310,13 +3318,15 @@ impl MultiBufferSnapshot {
.symbols_containing(anchor.text_anchor, theme)
.into_iter()
.flatten()
.map(|item| OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(excerpt_id, item.range.start)
..self.anchor_in_excerpt(excerpt_id, item.range.end),
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
.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)?,
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
})
})
.collect(),
))