wip getting ready to try changing anchorseektarget

Cole Miller and Conrad Irwin created

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/agent_ui/src/inline_assistant.rs       |   2 
crates/agent_ui/src/message_editor.rs         |   2 
crates/agent_ui/src/text_thread_editor.rs     |  10 
crates/editor/src/display_map.rs              |   8 
crates/editor/src/document_colors.rs          |   2 
crates/editor/src/editor.rs                   |  14 
crates/editor/src/element.rs                  |   2 
crates/editor/src/hover_links.rs              |   6 
crates/editor/src/hover_popover.rs            |   2 
crates/git_ui/src/conflict_view.rs            |  11 
crates/language_tools/src/syntax_tree_view.rs |   2 
crates/multi_buffer/src/anchor.rs             |  30 +
crates/multi_buffer/src/multi_buffer.rs       | 182 +++++++++++-----
crates/multi_buffer/src/multi_buffer_tests.rs |   4 
crates/multi_buffer/src/path_key.rs           | 226 ++++++++------------
crates/multi_buffer/src/transaction.rs        |  80 ++----
crates/outline_panel/src/outline_panel.rs     |   2 
17 files changed, 291 insertions(+), 294 deletions(-)

Detailed changes

crates/agent_ui/src/inline_assistant.rs 🔗

@@ -1999,7 +1999,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
                         }
 
                         let multibuffer_snapshot = multibuffer.read(cx);
-                        multibuffer_snapshot.anchor_range_in_excerpt(excerpt_id, action.range)
+                        multibuffer_snapshot.anchor_range_in_buffer(excerpt_id, action.range)
                     })
                 })
                 .context("invalid range")?;

crates/agent_ui/src/message_editor.rs 🔗

@@ -1666,7 +1666,7 @@ mod tests {
         editor.update_in(cx, |editor, window, cx| {
             let snapshot = editor.buffer().read(cx).snapshot(cx);
             let range = snapshot
-                .anchor_range_in_excerpt(excerpt_id, completion.replace_range)
+                .anchor_range_in_buffer(excerpt_id, completion.replace_range)
                 .unwrap();
             editor.edit([(range, completion.new_text)], cx);
             (completion.confirm.unwrap())(CompletionIntent::Complete, window, cx);

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -740,7 +740,7 @@ impl TextThreadEditor {
                             };
 
                             let range = buffer
-                                .anchor_range_in_excerpt(excerpt_id, command.source_range.clone())
+                                .anchor_range_in_buffer(excerpt_id, command.source_range.clone())
                                 .unwrap();
                             Crease::inline(range, placeholder, render_toggle, render_trailer)
                         }),
@@ -813,7 +813,7 @@ impl TextThreadEditor {
                     let (excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap();
 
                     let range = buffer
-                        .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
+                        .anchor_range_in_buffer(excerpt_id, invoked_slash_command.range.clone())
                         .unwrap();
                     editor.remove_folds_with_type(
                         &[range],
@@ -833,7 +833,7 @@ impl TextThreadEditor {
                     let (excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap();
                     let context = self.text_thread.downgrade();
                     let range = buffer
-                        .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
+                        .anchor_range_in_buffer(excerpt_id, invoked_slash_command.range.clone())
                         .unwrap();
                     let crease = Crease::inline(
                         range,
@@ -875,7 +875,7 @@ impl TextThreadEditor {
             let mut creases = Vec::new();
             for (section, status) in sections {
                 let range = buffer
-                    .anchor_range_in_excerpt(excerpt_id, section.range)
+                    .anchor_range_in_buffer(excerpt_id, section.range)
                     .unwrap();
                 let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
                 buffer_rows_to_fold.insert(buffer_row);
@@ -924,7 +924,7 @@ impl TextThreadEditor {
             let mut creases = Vec::new();
             for section in sections {
                 let range = buffer
-                    .anchor_range_in_excerpt(excerpt_id, section.range)
+                    .anchor_range_in_buffer(excerpt_id, section.range)
                     .unwrap();
                 let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
                 buffer_rows_to_fold.insert(buffer_row);

crates/editor/src/display_map.rs 🔗

@@ -1054,9 +1054,9 @@ impl DisplayMap {
 
         let base_placeholder = self.fold_placeholder.clone();
         let creases = ranges.into_iter().filter_map(|folding_range| {
-            let mb_range = excerpt_ids.iter().find_map(|&id| {
-                snapshot.anchor_range_in_excerpt(id, folding_range.range.clone())
-            })?;
+            let mb_range = excerpt_ids
+                .iter()
+                .find_map(|&id| snapshot.anchor_range_in_buffer(id, folding_range.range.clone()))?;
             let placeholder = if let Some(collapsed_text) = folding_range.collapsed_text {
                 FoldPlaceholder {
                     render: Arc::new({
@@ -1989,7 +1989,7 @@ impl DisplaySnapshot {
                 let start_anchor = buffer.anchor_before(buffer_range.start);
                 let end_anchor = buffer.anchor_after(buffer_range.end);
                 let mb_range =
-                    multibuffer.anchor_range_in_excerpt(excerpt_id, start_anchor..end_anchor)?;
+                    multibuffer.anchor_range_in_buffer(excerpt_id, start_anchor..end_anchor)?;
                 Some(mb_range.start.to_offset(multibuffer)..mb_range.end.to_offset(multibuffer))
             });
 

crates/editor/src/document_colors.rs 🔗

@@ -254,7 +254,7 @@ impl Editor {
                                         buffer_snapshot.clip_point_utf16(color_end, Bias::Right),
                                     );
                                     let Some(range) = multi_buffer_snapshot
-                                        .anchor_range_in_excerpt(*excerpt_id, start..end)
+                                        .anchor_range_in_buffer(*excerpt_id, start..end)
                                     else {
                                         continue;
                                     };

crates/editor/src/editor.rs 🔗

@@ -8620,7 +8620,7 @@ impl Editor {
             .into_iter()
             .flat_map(|(range, new_text)| {
                 Some((
-                    multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
+                    multibuffer.anchor_range_in_buffer(excerpt_id, range)?,
                     new_text,
                 ))
             })
@@ -24114,14 +24114,14 @@ impl Editor {
                     excerpts: excerpts.clone(),
                 });
             }
-            multi_buffer::Event::BuffersRemoved {
-                ids,
-                removed_buffer_ids,
-            } => {
+            multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
                 if let Some(inlay_hints) = &mut self.inlay_hints {
                     inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
                 }
-                self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
+                self.refresh_inlay_hints(
+                    InlayHintRefreshReason::ExcerptsRemoved(removed_buffer_ids.clone()),
+                    cx,
+                );
                 for buffer_id in removed_buffer_ids {
                     self.registered_buffers.remove(buffer_id);
                     self.tasks
@@ -24687,7 +24687,7 @@ impl Editor {
                                         let range = buffer_snapshot.anchor_before(range.start)
                                             ..buffer_snapshot.anchor_after(range.end);
                                         multibuffer_snapshot
-                                            .anchor_range_in_excerpt(excerpt_id, range)
+                                            .anchor_range_in_buffer(excerpt_id, range)
                                             .unwrap()
                                     }));
                                 },

crates/editor/src/element.rs 🔗

@@ -7888,7 +7888,7 @@ 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_range_in_excerpt(excerpt_id, *clipped_start..*clipped_end)?;
+                            .anchor_range_in_buffer(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 {

crates/editor/src/hover_links.rs 🔗

@@ -365,7 +365,7 @@ pub fn show_link_definition(
                         this.read_with(cx, |_, _| {
                             let range = maybe!({
                                 let range =
-                                    snapshot.anchor_range_in_excerpt(excerpt_id, url_range)?;
+                                    snapshot.anchor_range_in_buffer(excerpt_id, url_range)?;
                                 Some(RangeInEditor::Text(range))
                             });
                             (range, vec![HoverLink::Url(url)])
@@ -376,7 +376,7 @@ pub fn show_link_definition(
                     {
                         let range = maybe!({
                             let range =
-                                snapshot.anchor_range_in_excerpt(excerpt_id, filename_range)?;
+                                snapshot.anchor_range_in_buffer(excerpt_id, filename_range)?;
                             Some(RangeInEditor::Text(range))
                         });
 
@@ -390,7 +390,7 @@ pub fn show_link_definition(
                                 (
                                     definition_result.iter().find_map(|link| {
                                         link.origin.as_ref().and_then(|origin| {
-                                            let range = snapshot.anchor_range_in_excerpt(
+                                            let range = snapshot.anchor_range_in_buffer(
                                                 excerpt_id,
                                                 origin.range.clone(),
                                             )?;

crates/editor/src/hover_popover.rs 🔗

@@ -477,7 +477,7 @@ fn show_hover(
                     .and_then(|range| {
                         let range = snapshot
                             .buffer_snapshot()
-                            .anchor_range_in_excerpt(excerpt_id, range)?;
+                            .anchor_range_in_buffer(excerpt_id, range)?;
                         Some(range)
                     })
                     .or_else(|| {

crates/git_ui/src/conflict_view.rs 🔗

@@ -237,7 +237,7 @@ fn conflicts_updated(
                 continue;
             };
             let excerpt_id = *excerpt_id;
-            let Some(range) = snapshot.anchor_range_in_excerpt(excerpt_id, conflict_range) else {
+            let Some(range) = snapshot.anchor_range_in_buffer(excerpt_id, conflict_range) else {
                 continue;
             };
             removed_highlighted_ranges.push(range.clone());
@@ -324,9 +324,9 @@ fn update_conflict_highlighting(
 ) -> Option<()> {
     log::debug!("update conflict highlighting for {conflict:?}");
 
-    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 outer = buffer.anchor_range_in_buffer(excerpt_id, conflict.range.clone())?;
+    let ours = buffer.anchor_range_in_buffer(excerpt_id, conflict.ours.clone())?;
+    let theirs = buffer.anchor_range_in_buffer(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;
@@ -470,8 +470,7 @@ pub(crate) fn resolve_conflict(
                     })
                     .ok()?;
                 let &(_, block_id) = &state.block_ids[ix];
-                let range =
-                    snapshot.anchor_range_in_excerpt(excerpt_id, resolved_conflict.range)?;
+                let range = snapshot.anchor_range_in_buffer(excerpt_id, resolved_conflict.range)?;
 
                 editor.remove_gutter_highlights::<ConflictsOuter>(vec![range.clone()], cx);
 

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -361,7 +361,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_range_in_excerpt(excerpt_id, range)?;
+        let range = multibuffer.anchor_range_in_buffer(excerpt_id, range)?;
         let key = cx.entity_id().as_u64() as usize;
 
         // Update the editor with the anchor range.

crates/multi_buffer/src/anchor.rs 🔗

@@ -95,7 +95,7 @@ impl ExcerptAnchor {
         self
     }
 
-    fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
+    pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
         let Some(self_path_key) = snapshot.path_keys_by_index.get(&self.path) else {
             panic!("anchor's path was never added to multibuffer")
         };
@@ -206,7 +206,7 @@ impl ExcerptAnchor {
     }
 
     fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
-        let Some(target) = snapshot.try_anchor_seek_target(*self) else {
+        let Some(target) = self.try_seek_target(snapshot) else {
             return false;
         };
         let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
@@ -230,13 +230,21 @@ impl ExcerptAnchor {
     }
 
     pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
-        let path_key = snapshot.path_for_anchor(*self);
+        self.try_seek_target(snapshot)
+            .expect("anchor is from different multi-buffer")
+    }
+
+    pub(crate) fn try_seek_target(
+        &self,
+        snapshot: &MultiBufferSnapshot,
+    ) -> Option<AnchorSeekTarget> {
+        let path_key = snapshot.try_path_for_anchor(*self)?;
         let buffer = snapshot.buffer_for_path(&path_key).cloned();
-        AnchorSeekTarget::Excerpt {
+        Some(AnchorSeekTarget::Excerpt {
             path_key,
             anchor: *self,
             snapshot: buffer,
-        }
+        })
     }
 }
 
@@ -338,12 +346,14 @@ impl Anchor {
         }
     }
 
-    pub(crate) fn try_seek_target(&self, arg: &MultiBufferSnapshot) -> Option<AnchorSeekTarget> {
-        todo!()
+    pub(crate) fn try_seek_target(
+        &self,
+        snapshot: &MultiBufferSnapshot,
+    ) -> Option<AnchorSeekTarget> {
         match self {
-            Anchor::Min => AnchorSeekTarget::Min,
-            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.seek_target(snapshot),
-            Anchor::Max => AnchorSeekTarget::Max,
+            Anchor::Min => Some(AnchorSeekTarget::Min),
+            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.try_seek_target(snapshot),
+            Anchor::Max => Some(AnchorSeekTarget::Max),
         }
     }
 }

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -60,7 +60,7 @@ use text::{
 use theme::SyntaxTheme;
 use ztracing::instrument;
 
-pub use self::path_key::{PathExcerptInsertResult, PathKey};
+pub use self::path_key::PathKey;
 
 pub static EXCERPT_CONTEXT_LINES: OnceLock<fn(&App) -> u32> = OnceLock::new();
 
@@ -96,7 +96,7 @@ pub struct MultiBuffer {
 pub struct PathKeyIndex(u64);
 
 pub struct ExcerptInfo {
-    path_key: PathKey,
+    path_key_index: PathKeyIndex,
     buffer_id: BufferId,
     range: ExcerptRange<text::Anchor>,
 }
@@ -168,7 +168,9 @@ impl MultiBufferDiffHunk {
 }
 
 pub type MultiBufferPoint = Point;
+/// ExcerptOffset is offset into the non-deleted text of the multibuffer
 type ExcerptOffset = ExcerptDimension<MultiBufferOffset>;
+/// ExcerptOffset is based on the non-deleted text of the multibuffer
 type ExcerptPoint = ExcerptDimension<Point>;
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
@@ -789,7 +791,7 @@ impl ExcerptRange<text::Anchor> {
 #[derive(Clone, Debug)]
 pub struct ExcerptSummary {
     path_key: PathKey,
-    max_anchor: Anchor,
+    max_anchor: text::Anchor,
     widest_line_number: u32,
     text: MBTextSummary,
     count: usize,
@@ -799,7 +801,7 @@ impl ExcerptSummary {
     pub fn min() -> Self {
         ExcerptSummary {
             path_key: PathKey::min(),
-            max_anchor: Anchor::Min,
+            max_anchor: text::Anchor::MIN,
             widest_line_number: 0,
             text: MBTextSummary::default(),
             count: 0,
@@ -1647,13 +1649,21 @@ impl MultiBuffer {
                 }
                 let start = text::Anchor::max(
                     &excerpt.range.context.start,
-                    &selection.start.text_anchor().unwrap_or(text::Anchor::MIN),
+                    &selection
+                        .start
+                        .excerpt_anchor()
+                        .map(|excerpt_anchor| excerpt_anchor.text_anchor())
+                        .unwrap_or(text::Anchor::MIN),
                     &excerpt.buffer,
                 )
                 .clone();
                 let end = text::Anchor::min(
                     &excerpt.range.context.end,
-                    &selection.end.text_anchor().unwrap_or(text::Anchor::MAX),
+                    &selection
+                        .end
+                        .excerpt_anchor()
+                        .map(|excerpt_anchor| excerpt_anchor.text_anchor())
+                        .unwrap_or(text::Anchor::MAX),
                     &excerpt.buffer,
                 )
                 .clone();
@@ -1771,11 +1781,7 @@ impl MultiBuffer {
     }
 
     #[ztracing::instrument(skip_all)]
-    pub fn excerpts_for_buffer(
-        &self,
-        buffer_id: BufferId,
-        cx: &App,
-    ) -> Vec<ExcerptRange<text::Anchor>> {
+    pub fn excerpts_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Vec<ExcerptInfo> {
         let mut excerpts = Vec::new();
         let snapshot = self.read(cx);
         let Some(path_key) = snapshot.path_keys_by_buffer.get(&buffer_id).cloned() else {
@@ -1785,10 +1791,10 @@ impl MultiBuffer {
         let mut cursor = snapshot.excerpts.cursor::<PathKey>(());
         cursor.seek_forward(&path_key, Bias::Left);
 
-        while let Some(item) = cursor.item()
-            && item.path_key == path_key
+        while let Some(excerpt) = cursor.item()
+            && excerpt.path_key == path_key
         {
-            excerpts.push(item.range.clone());
+            excerpts.push(excerpt.info());
             cursor.next()
         }
         excerpts
@@ -1845,18 +1851,14 @@ impl MultiBuffer {
         let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&offset);
         let excerpt = cursor.excerpt().or(snapshot.excerpts.last())?;
-        Some(ExcerptInfo {
-            path_key: excerpt.path_key.clone(),
-            buffer_id: excerpt.buffer.remote_id(),
-            range: excerpt.range.clone(),
-        })
+        Some(excerpt.info())
     }
 
     pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option<Entity<Buffer>> {
         let buffer_id = match anchor {
             Anchor::Min => self.snapshot(cx).excerpts.first()?.buffer.remote_id(),
             Anchor::Max => self.snapshot(cx).excerpts.last()?.buffer.remote_id(),
-            Anchor::Text { buffer_id, .. } => buffer_id,
+            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.buffer_id,
         };
         self.buffer(buffer_id)
     }
@@ -1895,22 +1897,32 @@ impl MultiBuffer {
     ) -> Option<Anchor> {
         let mut found = None;
         let snapshot = buffer.read(cx).snapshot();
-        for range in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
-            let start = range.context.start.to_point(&snapshot);
-            let end = range.context.end.to_point(&snapshot);
-            if start <= point && point < end {
-                found = Some(snapshot.clip_point(point, Bias::Left));
+        let text_anchor = snapshot.anchor_after(&point);
+        for excerpt in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
+            if excerpt.range.contains(&text_anchor, &snapshot) {
+                found = Some(Anchor::in_buffer(excerpt.path_key_index, text_anchor));
                 break;
             }
-            if point < start {
-                found = Some(start);
-            }
-            if point > end {
-                found = Some(end);
+            if excerpt
+                .range
+                .context
+                .start
+                .cmp(&text_anchor, &snapshot)
+                .is_gt()
+            {
+                found = Some(Anchor::in_buffer(
+                    excerpt.path_key_index,
+                    excerpt.range.context.start,
+                ));
+                break;
             }
+            found = Some(Anchor::in_buffer(
+                excerpt.path_key_index,
+                excerpt.range.context.end,
+            ));
         }
 
-        Some(Anchor::text(snapshot.anchor_after(found?)))
+        found
     }
 
     pub fn buffer_anchor_to_anchor(
@@ -1921,9 +1933,9 @@ impl MultiBuffer {
         cx: &App,
     ) -> Option<Anchor> {
         let snapshot = buffer.read(cx).snapshot();
-        for range in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
-            if range.contains(&anchor, &snapshot) {
-                return Some(Anchor::text(anchor));
+        for excerpt in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
+            if excerpt.range.contains(&anchor, &snapshot) {
+                return Some(Anchor::in_buffer(excerpt.path_key_index, anchor));
             }
         }
 
@@ -5377,12 +5389,15 @@ impl MultiBufferSnapshot {
 
     /// 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_range_in_excerpt(
+    pub fn anchor_range_in_buffer(
         &self,
-        excerpt_id: ExcerptId,
+        excerpt_id: BufferId,
         text_anchor: Range<text::Anchor>,
     ) -> Option<Range<Anchor>> {
-        let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
+        let path_key_index = self.first_excerpt_for_buffer(buffer_id)?.path_key_index;
+
+        let target = self.anchor_see
+        self.excerpts.cursor.find(&snapshot, (path_key_index, anchor))
 
         Some(
             Self::anchor_in_excerpt_(excerpt, text_anchor.start)?
@@ -6293,17 +6308,17 @@ impl MultiBufferSnapshot {
                 .flat_map(|item| {
                     Some(OutlineItem {
                         depth: item.depth,
-                        range: self.anchor_range_in_excerpt(excerpt_id, item.range)?,
+                        range: self.anchor_range_in_buffer(excerpt_id, item.range)?,
                         source_range_for_text: self
-                            .anchor_range_in_excerpt(excerpt_id, item.source_range_for_text)?,
+                            .anchor_range_in_buffer(excerpt_id, item.source_range_for_text)?,
                         text: item.text,
                         highlight_ranges: item.highlight_ranges,
                         name_ranges: item.name_ranges,
                         body_range: item.body_range.and_then(|body_range| {
-                            self.anchor_range_in_excerpt(excerpt_id, body_range)
+                            self.anchor_range_in_buffer(excerpt_id, body_range)
                         }),
                         annotation_range: item.annotation_range.and_then(|annotation_range| {
-                            self.anchor_range_in_excerpt(excerpt_id, annotation_range)
+                            self.anchor_range_in_buffer(excerpt_id, annotation_range)
                         }),
                     })
                 })
@@ -6371,6 +6386,14 @@ impl MultiBufferSnapshot {
         self.path_keys_by_buffer.get(&buffer_id)
     }
 
+    pub fn first_excerpt_for_buffer(&self, buffer_id: BufferId) -> Option<&Excerpt> {
+        let path_key = self.path_keys_by_buffer.get(&buffer_id)?;
+        let (_, _, first_excerpt) =
+            self.excerpts
+                .find::<ExcerptSummary, _>((), path_key, Bias::Left);
+        first_excerpt
+    }
+
     pub fn buffer_for_id(&self, id: BufferId) -> Option<&BufferSnapshot> {
         self.buffer_for_path(self.path_keys_by_buffer.get(&id)?)
     }
@@ -6609,11 +6632,15 @@ impl MultiBufferSnapshot {
         excerpt_edits
     }
 
-    fn excerpt_ranges_for_path(
-        &self,
-        path_key: &PathKey,
-    ) -> impl Iterator<Item = ExcerptRange<text::Anchor>> {
-        todo!()
+    fn excerpts_for_path<'a>(
+        &'a self,
+        path_key: &'a PathKey,
+    ) -> impl Iterator<Item = ExcerptInfo> + 'a {
+        let mut cursor = self.excerpts.cursor::<ExcerptSummary>(());
+        cursor.seek(path_key, Bias::Left);
+        cursor
+            .take_while(move |item| &item.path_key == path_key)
+            .map(|excerpt| excerpt.info())
     }
 }
 
@@ -6632,27 +6659,46 @@ impl MultiBufferSnapshot {
     #[cfg(any(test, feature = "test-support"))]
     fn check_invariants(&self) {
         let excerpts = self.excerpts.items(());
-        let excerpt_ids = self.excerpt_ids.items(());
 
-        for (ix, excerpt) in excerpts.iter().enumerate() {
-            if ix == 0 {
-                if excerpt.locator <= Locator::min() {
-                    panic!("invalid first excerpt locator {:?}", excerpt.locator);
-                }
-            } else if excerpt.locator <= excerpts[ix - 1].locator {
-                panic!("excerpts are out-of-order: {:?}", excerpts);
-            }
-        }
+        let all_buffer_path_keys = HashSet::from_iter(self.path_keys_by_buffer.values().cloned());
+        let all_excerpt_path_keys = HashSet::from_iter(excerpts.iter().map(|e| e.path_key.clone()));
 
-        for (ix, entry) in excerpt_ids.iter().enumerate() {
-            if ix == 0 {
-                if entry.id.cmp(&ExcerptId::min(), self).is_le() {
-                    panic!("invalid first excerpt id {:?}", entry.id);
+        for (ix, excerpt) in excerpts.iter().enumerate() {
+            if ix > 0 {
+                let prev = &excerpts[ix - 1];
+
+                if excerpt.path_key < prev.path_key {
+                    panic!("excerpt path_keys are out-of-order: {:?}", excerpts);
+                } else if excerpt.path_key == prev.path_key {
+                    if excerpt
+                        .start_anchor()
+                        .cmp(&prev.end_anchor(), &self)
+                        .is_le()
+                    {
+                        panic!("excerpt anchors are out-of-order: {:?}", excerpts);
+                    }
+                    if excerpt
+                        .start_anchor()
+                        .cmp(&excerpt.end_anchor(), &self)
+                        .is_ge()
+                    {
+                        panic!("excerpt with backward range: {:?}", excerpts);
+                    }
                 }
-            } else if entry.id <= excerpt_ids[ix - 1].id {
-                panic!("excerpt ids are out-of-order: {:?}", excerpt_ids);
             }
+            assert!(
+                all_buffer_path_keys.contains(&excerpt.path_key),
+                "excerpt path key not found in active path keys: {:?}",
+                excerpt.path_key
+            );
+            assert_eq!(
+                self.path_keys_by_index.get(&excerpt.path_key_index),
+                Some(&excerpt.path_key),
+                "excerpt path key index does not match path key: {:?}",
+                excerpt.path_key,
+            );
         }
+        assert_eq!(all_buffer_path_keys, all_excerpt_path_keys);
 
         if self.diff_transforms.summary().input != self.excerpts.summary().text {
             panic!(
@@ -7009,12 +7055,14 @@ where
 impl Excerpt {
     fn new(
         path_key: PathKey,
+        path_key_index: PathKeyIndex,
         buffer: Arc<BufferSnapshot>,
         range: ExcerptRange<text::Anchor>,
         has_trailing_newline: bool,
     ) -> Self {
         Excerpt {
             path_key,
+            path_key_index,
             max_buffer_row: range.context.end.to_point(&buffer).row,
             text_summary: buffer
                 .text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)),
@@ -7024,6 +7072,14 @@ impl Excerpt {
         }
     }
 
+    fn info(&self) -> ExcerptInfo {
+        ExcerptInfo {
+            path_key_index: self.path_key_index,
+            buffer_id: self.buffer.remote_id(),
+            range: self.range.clone(),
+        }
+    }
+
     fn chunks_in_range(&self, range: Range<usize>, language_aware: bool) -> ExcerptChunks<'_> {
         let content_start = self.range.context.start.to_offset(&self.buffer);
         let chunks_start = content_start + range.start;
@@ -7247,7 +7303,7 @@ impl sum_tree::Item for Excerpt {
             text += TextSummary::from("\n");
         }
         ExcerptSummary {
-            path_key: self.path_key,
+            path_key: self.path_key.clone(),
             max_anchor: self.range.context.end,
             widest_line_number: self.max_buffer_row,
             text: text.into(),

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -3533,8 +3533,8 @@ fn test_history(cx: &mut App) {
         assert_eq!(
             multibuffer.edited_ranges_for_transaction(transaction_1, cx),
             &[
-                Point::new(0, 0)..Point::new(0, 2),
-                Point::new(1, 0)..Point::new(1, 2)
+                MultiBufferOffset(0)..MultiBufferOffset(2),
+                MultiBufferOffset(7)..MultiBufferOffset(9),
             ]
         );
 

crates/multi_buffer/src/path_key.rs 🔗

@@ -5,20 +5,17 @@ use std::{
     sync::Arc,
 };
 
-use collections::HashSet;
 use gpui::{App, AppContext, Context, Entity};
-use itertools::Itertools;
 use language::{Buffer, BufferSnapshot};
 use rope::Point;
 use sum_tree::{Dimensions, SumTree};
-use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch};
+use text::{Bias, Edit, OffsetRangeExt, Patch};
 use util::rel_path::RelPath;
 use ztracing::instrument;
 
 use crate::{
     Anchor, BufferState, DiffChangeKind, Event, Excerpt, ExcerptOffset, ExcerptRange,
-    ExcerptSummary, ExpandExcerptDirection, MultiBuffer, MultiBufferOffset, PathKeyIndex,
-    build_excerpt_ranges,
+    ExcerptSummary, ExpandExcerptDirection, MultiBuffer, PathKeyIndex, build_excerpt_ranges,
 };
 
 #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
@@ -64,31 +61,19 @@ impl PathKey {
 }
 
 impl MultiBuffer {
-    pub fn paths(&self) -> impl Iterator<Item = &PathKey> + '_ {
-        self.excerpts_by_path.keys()
-    }
-
-    pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator<Item = ExcerptId> {
-        self.excerpts_by_path
-            .get(path)
-            .map(|excerpts| excerpts.as_slice())
-            .unwrap_or_default()
-            .iter()
-            .copied()
-    }
-
     pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option<Entity<Buffer>> {
-        let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
-        let snapshot = self.read(cx);
-        let excerpt = snapshot.excerpt(*excerpt_id)?;
+        let snapshot = self.snapshot(cx);
+        let excerpt = snapshot.excerpts_for_path(path).next()?;
         self.buffer(excerpt.buffer_id)
     }
 
     pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
-        let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
-        let snapshot = self.read(cx);
-        let excerpt = snapshot.excerpt(*excerpt_id)?;
-        Some(Anchor::text(excerpt.id, excerpt.range.context.start))
+        let snapshot = self.snapshot(cx);
+        let excerpt = snapshot.excerpts_for_path(path).next()?;
+        Some(Anchor::in_buffer(
+            excerpt.path_key_index,
+            excerpt.range.context.start,
+        ))
     }
 
     pub fn set_excerpts_for_buffer(
@@ -118,12 +103,14 @@ impl MultiBuffer {
             build_excerpt_ranges(ranges.clone(), context_line_count, &buffer_snapshot);
 
         let (new, _) = Self::merge_excerpt_ranges(&excerpt_ranges);
-        let inserted =
+        let (inserted, path_key_index) =
             self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, new, cx);
         // todo!() move this into the callers that care
         let anchors = ranges
             .into_iter()
-            .map(|range| Anchor::range_in_buffer(buffer_snapshot.anchor_range_around(range)))
+            .map(|range| {
+                Anchor::range_in_buffer(path_key_index, buffer_snapshot.anchor_range_around(range))
+            })
             .collect::<Vec<_>>();
         (anchors, inserted)
     }
@@ -137,13 +124,16 @@ impl MultiBuffer {
         cx: &mut Context<Self>,
     ) -> (Vec<Range<Anchor>>, bool) {
         let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
-        let inserted =
+        let (inserted, path_key_index) =
             self.set_merged_excerpt_ranges_for_path(path, buffer, buffer_snapshot, new, cx);
         // todo!() move this into the callers that care
         let anchors = excerpt_ranges
             .into_iter()
             .map(|range| {
-                Anchor::range_in_buffer(buffer_snapshot.anchor_range_around(range.primary))
+                Anchor::range_in_buffer(
+                    path_key_index,
+                    buffer_snapshot.anchor_range_around(range.primary),
+                )
             })
             .collect::<Vec<_>>();
         (anchors, inserted)
@@ -174,7 +164,7 @@ impl MultiBuffer {
 
             multi_buffer
                 .update(&mut app, move |multi_buffer, cx| {
-                    multi_buffer.set_merged_excerpt_ranges_for_path(
+                    let (_, path_key_index) = multi_buffer.set_merged_excerpt_ranges_for_path(
                         path_key,
                         buffer,
                         &buffer_snapshot,
@@ -183,7 +173,7 @@ impl MultiBuffer {
                     );
                     ranges
                         .into_iter()
-                        .map(|range| Anchor::range_in_buffer(range))
+                        .map(|range| Anchor::range_in_buffer(path_key_index, range))
                         .collect()
                 })
                 .ok()
@@ -199,7 +189,10 @@ impl MultiBuffer {
         cx: &mut Context<Self>,
     ) {
         let snapshot = self.snapshot(cx);
-        let mut sorted_anchors = anchors.into_iter().collect::<Vec<_>>();
+        let mut sorted_anchors = anchors
+            .into_iter()
+            .filter_map(|anchor| anchor.excerpt_anchor())
+            .collect::<Vec<_>>();
         sorted_anchors.sort_by(|a, b| a.cmp(b, &snapshot));
         let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
         let mut sorted_anchors = sorted_anchors.into_iter().peekable();
@@ -217,9 +210,7 @@ impl MultiBuffer {
                 && snapshot.path_for_anchor(anchor) == path
             {
                 sorted_anchors.next();
-                let Some(target) = snapshot.anchor_seek_target(anchor) else {
-                    continue;
-                };
+                let target = anchor.seek_target(&snapshot);
                 // Move to the next excerpt to be expanded, and push unchanged ranges for intervening excerpts
                 expanded_ranges.extend(
                     cursor
@@ -295,7 +286,7 @@ impl MultiBuffer {
         buffer_snapshot: &BufferSnapshot,
         new: Vec<ExcerptRange<Point>>,
         cx: &mut Context<Self>,
-    ) -> bool {
+    ) -> (bool, PathKeyIndex) {
         let anchor_ranges = new
             .into_iter()
             .map(|r| ExcerptRange {
@@ -306,6 +297,29 @@ impl MultiBuffer {
         self.update_path_excerpts(path, buffer, buffer_snapshot, &anchor_ranges, cx)
     }
 
+    fn get_or_create_path_key_index(&mut self, path_key: &PathKey) -> PathKeyIndex {
+        let mut snapshot = self.snapshot.borrow_mut();
+        let existing = snapshot
+            .path_keys_by_index
+            .iter()
+            // todo!() perf? (but ExcerptIdMapping was doing this)
+            .find(|(_, existing_path)| existing_path == &path_key)
+            .map(|(index, _)| *index);
+
+        if let Some(existing) = existing {
+            return existing;
+        }
+
+        let index = snapshot
+            .path_keys_by_index
+            .last()
+            .map(|(index, _)| PathKeyIndex(index.0 + 1))
+            .unwrap_or(PathKeyIndex(0));
+        snapshot.path_keys_by_index.insert(index, path_key.clone());
+        index
+    }
+
+    // todo!() re-instate nonshrinking version for project diff / diagnostics
     pub fn update_path_excerpts<'a>(
         &mut self,
         path_key: PathKey,
@@ -313,25 +327,27 @@ impl MultiBuffer {
         buffer_snapshot: &BufferSnapshot,
         to_insert: &Vec<ExcerptRange<text::Anchor>>,
         cx: &mut Context<Self>,
-    ) -> bool {
+    ) -> (bool, PathKeyIndex) {
+        let path_key_index = self.get_or_create_path_key_index(&path_key);
+        if let Some(old_path_key) = self
+            .snapshot(cx)
+            .path_for_buffer(buffer_snapshot.remote_id())
+            && old_path_key != &path_key
+        {
+            self.remove_excerpts_for_path(old_path_key.clone(), cx);
+        }
+
         if to_insert.len() == 0 {
             self.remove_excerpts_for_path(path_key.clone(), cx);
-            if let Some(old_path_key) = self
-                .snapshot(cx)
-                .path_for_buffer(buffer_snapshot.remote_id())
-                && old_path_key != &path_key
-            {
-                self.remove_excerpts_for_path(old_path_key.clone(), cx);
-            }
 
-            return false;
+            return (false, path_key_index);
         }
         assert_eq!(self.history.transaction_depth(), 0);
         self.sync_mut(cx);
 
         let buffer_snapshot = buffer.read(cx).snapshot();
         let buffer_id = buffer_snapshot.remote_id();
-        let buffer_state = self.buffers.entry(buffer_id).or_insert_with(|| {
+        self.buffers.entry(buffer_id).or_insert_with(|| {
             self.buffer_changed_since_sync.replace(true);
             buffer.update(cx, |buffer, _| {
                 buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
@@ -359,39 +375,6 @@ impl MultiBuffer {
         let mut patch = Patch::empty();
         let mut added_new_excerpt = false;
 
-        let path_key_index = snapshot
-            .path_keys_by_index
-            .iter()
-            // todo!() perf? (but ExcerptIdMapping was doing this)
-            .find(|(_, existing_path)| existing_path == &&path_key)
-            .map(|(index, _)| *index);
-        let path_key_index = path_key_index.unwrap_or_else(|| {
-            let index = snapshot
-                .path_keys_by_index
-                .last()
-                .map(|(index, _)| PathKeyIndex(index.0 + 1))
-                .unwrap_or(PathKeyIndex(0));
-            snapshot.path_keys_by_index.insert(index, path_key.clone());
-            index
-        });
-        let old_path_key = snapshot
-            .path_keys_by_buffer
-            .insert_or_replace(buffer_id, path_key.clone());
-        // handle the case where the buffer's path key has changed by
-        // removing any old excerpts for the buffer
-        if let Some(old_path_key) = &old_path_key
-            && old_path_key < &path_key
-        {
-            new_excerpts.append(cursor.slice(old_path_key, Bias::Left), ());
-            let before = cursor.position.1;
-            cursor.seek_forward(old_path_key, Bias::Right);
-            let after = cursor.position.1;
-            patch.push(Edit {
-                old: before..after,
-                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
-            });
-        }
-
         new_excerpts.append(cursor.slice(&path_key, Bias::Left), ());
 
         // handle the case where the path key used to be associated
@@ -417,7 +400,7 @@ impl MultiBuffer {
             let Some(next_excerpt) = to_insert.peek() else {
                 break;
             };
-            if &&excerpt.range == next_excerpt {
+            if &excerpt.range == *next_excerpt {
                 new_excerpts.push(excerpt.clone(), ());
                 to_insert.next();
                 cursor.next();
@@ -447,6 +430,7 @@ impl MultiBuffer {
                 new_excerpts.push(
                     Excerpt::new(
                         path_key.clone(),
+                        path_key_index,
                         buffer_snapshot.clone(),
                         next_excerpt.clone(),
                         to_insert.peek().is_some(),
@@ -470,28 +454,9 @@ impl MultiBuffer {
             new: new_excerpts.summary().len()..new_excerpts.summary().len(),
         });
 
-        // handle the case where the buffer's path key has changed by
-        // removing any old excerpts for the buffer
-        if let Some(old_path_key) = &old_path_key
-            && old_path_key > &path_key
-        {
-            new_excerpts.append(cursor.slice(old_path_key, Bias::Left), ());
-            let before = cursor.position.1;
-            cursor.seek_forward(old_path_key, Bias::Right);
-            let after = cursor.position.1;
-            patch.push(Edit {
-                old: before..after,
-                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
-            });
-        }
-
         let suffix = cursor.suffix();
         let changed_trailing_excerpt = suffix.is_empty();
         new_excerpts.append(suffix, ());
-        let new_ranges = snapshot
-            .excerpt_ranges_for_path(&path_key)
-            .map(|range| range.context)
-            .collect();
         drop(cursor);
         snapshot.excerpts = new_excerpts;
         if changed_trailing_excerpt {
@@ -513,61 +478,56 @@ impl MultiBuffer {
         cx.emit(Event::BufferUpdated {
             buffer,
             path_key: path_key.clone(),
-            ranges: new_ranges,
+            ranges: to_insert.map(|range| range.context.clone()).collect(),
         });
         cx.notify();
 
-        added_new_excerpt
+        (added_new_excerpt, path_key_index)
     }
 
     pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
-        let mut patch = Patch::empty();
+        assert_eq!(self.history.transaction_depth(), 0);
+        self.sync_mut(cx);
 
         let mut snapshot = self.snapshot.get_mut();
         let mut cursor = snapshot
             .excerpts
             .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
         let mut new_excerpts = SumTree::new(());
-
-        if let Some(old_path_key) = old_path_key
-            && old_path_key < path_key
+        new_excerpts.append(cursor.slice(&path, Bias::Left), ());
+        let edit_start = cursor.position.1;
+        let mut buffer_id = None;
+        if let Some(excerpt) = cursor.item()
+            && excerpt.path_key == path
         {
-            new_excerpts.append(cursor.slice(&old_path_key, Bias::Left), ());
-            let before = cursor.position.1;
-            cursor.seek_forward(&old_path_key, Bias::Right);
-            let after = cursor.position.1;
-            patch.push(Edit {
-                old: before..after,
-                new: new_excerpts.summary().len()..new_excerpts.summary().len(),
-            });
+            buffer_id = Some(excerpt.buffer.remote_id());
         }
-
+        cursor.seek(&path, Bias::Right);
+        let edit_end = cursor.position.1;
         let suffix = cursor.suffix();
         let changed_trailing_excerpt = suffix.is_empty();
         new_excerpts.append(suffix, ());
 
-        for buffer_id in removed_excerpts_for_buffers {
-            match self.buffers.get(&buffer_id) {
-                Some(buffer_state) => {
-                    snapshot
-                        .buffer_locators
-                        .insert(buffer_id, buffer_state.excerpts.iter().cloned().collect());
-                }
-                None => {
-                    snapshot.buffer_locators.remove(&buffer_id);
-                }
-            }
+        let edit = Edit {
+            old: edit_start..edit_end,
+            new: edit_start..edit_start,
+        };
+
+        if let Some(buffer_id) = buffer_id {
+            snapshot.path_keys_by_buffer.remove(&buffer_id);
+            self.buffers.remove(&buffer_id);
+            cx.emit(Event::BuffersRemoved {
+                removed_buffer_ids: vec![buffer_id],
+            })
         }
+        drop(cursor);
         snapshot.excerpts = new_excerpts;
         if changed_trailing_excerpt {
             snapshot.trailing_excerpt_update_count += 1;
         }
 
-        let edits = Self::sync_diff_transforms(
-            &mut snapshot,
-            patch.into_inner(),
-            DiffChangeKind::BufferEdited,
-        );
+        let edits =
+            Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
         if !edits.is_empty() {
             self.subscriptions.publish(edits);
         }
@@ -575,12 +535,6 @@ impl MultiBuffer {
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
-        // todo!() is this right? different event?
-        cx.emit(Event::BufferUpdated {
-            buffer,
-            path_key: path.clone(),
-            ranges: Vec::new(),
-        });
         cx.notify();
     }
 }

crates/multi_buffer/src/transaction.rs 🔗

@@ -2,15 +2,15 @@ use gpui::{App, Context, Entity};
 use language::{self, Buffer, TransactionId};
 use std::{
     collections::HashMap,
-    ops::{AddAssign, Range, Sub},

+    ops::Range,

     time::{Duration, Instant},
 };
 use sum_tree::Bias;
 use text::BufferId;
 
-use crate::{BufferState, MultiBufferDimension};

+use crate::{Anchor, BufferState, MultiBufferOffset};

 
-use super::{Event, ExcerptSummary, MultiBuffer};

+use super::{Event, MultiBuffer};

 
 #[derive(Clone)]
 pub(super) struct History {
@@ -314,71 +314,49 @@ impl MultiBuffer {
         }
     }
 
-    pub fn edited_ranges_for_transaction<D>(

+    pub fn edited_ranges_for_transaction(

         &self,
         transaction_id: TransactionId,
         cx: &App,
-    ) -> Vec<Range<D>>

-    where

-        D: MultiBufferDimension

-            + Ord

-            + Sub<D, Output = D::TextDimension>

-            + AddAssign<D::TextDimension>,

-        D::TextDimension: PartialOrd + Sub<D::TextDimension, Output = D::TextDimension>,

-    {

+    ) -> Vec<Range<MultiBufferOffset>> {

         let Some(transaction) = self.history.transaction(transaction_id) else {
             return Vec::new();
         };
 
-        let mut ranges = Vec::new();

         let snapshot = self.read(cx);
-        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());

+        let mut buffer_anchors = Vec::new();

 
         for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
-            let Some(buffer_state) = self.buffers.get(buffer_id) else {

+            let Some(buffer) = self.buffer(*buffer_id) else {

+                continue;

+            };

+            let Some(excerpt) = snapshot.first_excerpt_for_buffer(*buffer_id) else {

                 continue;
             };
 
-            let buffer = buffer_state.buffer.read(cx);

-            for range in

-                buffer.edited_ranges_for_transaction_id::<D::TextDimension>(*buffer_transaction)

+            for range in buffer

+                .read(cx)

+                .edited_ranges_for_transaction_id::<usize>(*buffer_transaction)

             {
-                for excerpt_id in &buffer_state.excerpts {

-                    cursor.seek(excerpt_id, Bias::Left);

-                    if let Some(excerpt) = cursor.item()

-                        && excerpt.locator == *excerpt_id

-                    {

-                        let excerpt_buffer_start = excerpt

-                            .range

-                            .context

-                            .start

-                            .summary::<D::TextDimension>(buffer);

-                        let excerpt_buffer_end = excerpt

-                            .range

-                            .context

-                            .end

-                            .summary::<D::TextDimension>(buffer);

-                        let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;

-                        if excerpt_range.contains(&range.start)

-                            && excerpt_range.contains(&range.end)

-                        {

-                            let excerpt_start = D::from_summary(&cursor.start().text);

-

-                            let mut start = excerpt_start;

-                            start += range.start - excerpt_buffer_start;

-                            let mut end = excerpt_start;

-                            end += range.end - excerpt_buffer_start;

-

-                            ranges.push(start..end);

-                            break;

-                        }

-                    }

-                }

+                buffer_anchors.push(Anchor::in_buffer(

+                    excerpt.path_key_index,

+                    excerpt.buffer.anchor_at(range.start, Bias::Left),

+                ));

+                buffer_anchors.push(Anchor::in_buffer(

+                    excerpt.path_key_index,

+                    excerpt.buffer.anchor_at(range.end, Bias::Right),

+                ));

             }
         }
+        buffer_anchors.sort_unstable_by(|a, b| a.cmp(b, &snapshot));

 
-        ranges.sort_by_key(|range| range.start);

-        ranges

+        snapshot

+            .summaries_for_anchors(buffer_anchors.iter())

+            .as_chunks::<2>()

+            .0

+            .iter()

+            .map(|&[s, e]| s..e)

+            .collect::<Vec<_>>()

     }
 
     pub fn merge_transactions(

crates/outline_panel/src/outline_panel.rs 🔗

@@ -3332,7 +3332,7 @@ impl OutlinePanel {
             .flat_map(|excerpt| excerpt.iter_outlines())
             .flat_map(|outline| {
                 let range = multi_buffer_snapshot
-                    .anchor_range_in_excerpt(excerpt_id, outline.range.clone())?;
+                    .anchor_range_in_buffer(excerpt_id, outline.range.clone())?;
                 Some((
                     range.start.to_display_point(&editor_snapshot)
                         ..range.end.to_display_point(&editor_snapshot),