diff --git a/crates/editor/src/linked_editing_ranges.rs b/crates/editor/src/linked_editing_ranges.rs index c883ec14fb4c50a11fb4dfba1031baebf4637f11..ab16fe7eb4bce28ef6bfee2c2bde1d52fda86561 100644 --- a/crates/editor/src/linked_editing_ranges.rs +++ b/crates/editor/src/linked_editing_ranges.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use gpui::{Context, Window}; +use gpui::{AppContext, Context, Window}; use itertools::Itertools; use std::{ops::Range, time::Duration}; use text::{AnchorRangeExt, BufferId, ToPoint}; @@ -59,8 +59,9 @@ pub(super) fn refresh_linked_ranges( let mut applicable_selections = Vec::new(); editor .update(cx, |editor, cx| { - let selections = editor.selections.all::(&editor.display_snapshot(cx)); - let snapshot = editor.buffer.read(cx).snapshot(cx); + let display_snapshot = editor.display_snapshot(cx); + let selections = editor.selections.all::(&display_snapshot); + let snapshot = display_snapshot.buffer_snapshot(); let buffer = editor.buffer.read(cx); for selection in selections { let cursor_position = selection.head(); @@ -90,14 +91,16 @@ pub(super) fn refresh_linked_ranges( let highlights = project .update(cx, |project, cx| { let mut linked_edits_tasks = vec![]; - for (buffer, start, end) in &applicable_selections { - let snapshot = buffer.read(cx).snapshot(); - let buffer_id = buffer.read(cx).remote_id(); - let linked_edits_task = project.linked_edits(buffer, *start, cx); - let highlights = move || async move { + let cx = cx.to_async(); + let highlights = async move { let edits = linked_edits_task.await.log_err()?; + let snapshot = cx + .read_entity(&buffer, |buffer, _| buffer.snapshot()) + .ok()?; + let buffer_id = snapshot.remote_id(); + // Find the range containing our current selection. // We might not find one, because the selection contains both the start and end of the contained range // (think of selecting <`html>foo` - even though there's a matching closing tag, the selection goes beyond the range of the opening tag) @@ -128,7 +131,7 @@ pub(super) fn refresh_linked_ranges( siblings.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot)); Some((buffer_id, siblings)) }; - linked_edits_tasks.push(highlights()); + linked_edits_tasks.push(highlights); } linked_edits_tasks }) diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 9d3034c0e9603ee37dca802e545af9a593b930a1..de559075403f53468e0b0cbeb4ecdc1754f4375b 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2354,6 +2354,7 @@ impl BufferSnapshot { self.visible_text.len() } else { debug_assert!(anchor.buffer_id == Some(self.remote_id)); + debug_assert!(self.version.observed(anchor.timestamp)); let anchor_key = InsertionFragmentKey { timestamp: anchor.timestamp, split_offset: anchor.offset, @@ -2377,10 +2378,7 @@ impl BufferSnapshot { .item() .filter(|insertion| insertion.timestamp == anchor.timestamp) else { - panic!( - "invalid anchor {:?}. buffer id: {}, version: {:?}", - anchor, self.remote_id, self.version - ); + self.panic_bad_anchor(anchor); }; let (start, _, item) = self @@ -2399,13 +2397,29 @@ impl BufferSnapshot { } } - fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { - self.try_fragment_id_for_anchor(anchor).unwrap_or_else(|| { + #[cold] + fn panic_bad_anchor(&self, anchor: &Anchor) -> ! { + if anchor.buffer_id.is_some_and(|id| id != self.remote_id) { + panic!( + "invalid anchor - buffer id does not match: anchor {anchor:?}; buffer id: {}, version: {:?}", + self.remote_id, self.version + ); + } else if !self.version.observed(anchor.timestamp) { + panic!( + "invalid anchor - snapshot has not observed lamport: {:?}; version: {:?}", + anchor, self.version + ); + } else { panic!( "invalid anchor {:?}. buffer id: {}, version: {:?}", - anchor, self.remote_id, self.version, - ) - }) + anchor, self.remote_id, self.version + ); + } + } + + fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { + self.try_fragment_id_for_anchor(anchor) + .unwrap_or_else(|| self.panic_bad_anchor(anchor)) } fn try_fragment_id_for_anchor(&self, anchor: &Anchor) -> Option<&Locator> {