Start to remove ExcerptId from multibuffer

Conrad Irwin created

Change summary

crates/agent_ui/src/connection_view/thread_view.rs |   2 
crates/debugger_ui/src/debugger_ui.rs              |   2 
crates/editor/src/display_map/block_map.rs         |  10 
crates/editor/src/editor.rs                        |  17 
crates/editor/src/element.rs                       |   8 
crates/editor/src/git/blame.rs                     |   2 
crates/editor/src/items.rs                         |   5 
crates/git_ui/src/git_panel.rs                     |   4 
crates/multi_buffer/src/anchor.rs                  | 350 ++++++---
crates/multi_buffer/src/multi_buffer.rs            | 544 ++++-----------
crates/multi_buffer/src/multi_buffer_tests.rs      |  10 
crates/multi_buffer/src/path_key.rs                |  16 
crates/vim/src/motion.rs                           |   4 
crates/vim/src/normal.rs                           |   2 
crates/vim/src/state.rs                            |   2 
15 files changed, 418 insertions(+), 560 deletions(-)

Detailed changes

crates/agent_ui/src/connection_view/thread_view.rs 🔗

@@ -5839,7 +5839,7 @@ impl ThreadView {
                         && let Some(agent_buffer) = agent_location.buffer.upgrade()
                         && agent_buffer.read(cx).remote_id() == buffer_id
                     {
-                        let anchor = editor::Anchor::in_buffer(excerpt_id, agent_location.position);
+                        let anchor = editor::Anchor::text(excerpt_id, agent_location.position);
                         editor.change_selections(Default::default(), window, cx, |selections| {
                             selections.select_anchor_ranges([anchor..anchor]);
                         })

crates/debugger_ui/src/debugger_ui.rs 🔗

@@ -299,7 +299,7 @@ pub fn init(cx: &mut App) {
                                     return;
                                 }
                                 maybe!({
-                                    let (buffer, position, _) = editor
+                                    let (buffer, position) = editor
                                         .update(cx, |editor, cx| {
                                             let cursor_point: language::Point = editor
                                                 .selections

crates/editor/src/display_map/block_map.rs 🔗

@@ -11,7 +11,7 @@ use collections::{Bound, HashMap, HashSet};
 use gpui::{AnyElement, App, EntityId, Pixels, Window};
 use language::{Patch, Point};
 use multi_buffer::{
-    Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint,
+    Anchor, ExcerptBoundaryInfo, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferPoint,
     MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
 };
 use parking_lot::Mutex;
@@ -339,15 +339,15 @@ struct Transform {
 pub enum Block {
     Custom(Arc<CustomBlock>),
     FoldedBuffer {
-        first_excerpt: ExcerptInfo,
+        first_excerpt: ExcerptBoundaryInfo,
         height: u32,
     },
     ExcerptBoundary {
-        excerpt: ExcerptInfo,
+        excerpt: ExcerptBoundaryInfo,
         height: u32,
     },
     BufferHeader {
-        excerpt: ExcerptInfo,
+        excerpt: ExcerptBoundaryInfo,
         height: u32,
     },
     Spacer {
@@ -2563,7 +2563,7 @@ impl BlockChunks<'_> {
 }
 
 pub struct StickyHeaderExcerpt<'a> {
-    pub excerpt: &'a ExcerptInfo,
+    pub excerpt: &'a ExcerptBoundaryInfo,
 }
 
 impl<'a> Iterator for BlockChunks<'a> {

crates/editor/src/editor.rs 🔗

@@ -150,7 +150,8 @@ use markdown::Markdown;
 use mouse_context_menu::MouseContextMenu;
 use movement::TextLayoutDetails;
 use multi_buffer::{
-    ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
+    ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
+    MultiBufferRow,
 };
 use parking_lot::Mutex;
 use persistence::DB;
@@ -892,7 +893,7 @@ pub trait Addon: 'static {
 
     fn render_buffer_header_controls(
         &self,
-        _: &ExcerptInfo,
+        _: &ExcerptBoundaryInfo,
         _: &Window,
         _: &App,
     ) -> Option<AnyElement> {
@@ -5359,7 +5360,7 @@ impl Editor {
             let row = cursor.row;
 
             let point = Point::new(row, 0);
-            let Some((buffer_handle, buffer_point, _)) =
+            let Some((buffer_handle, buffer_point)) =
                 self.buffer.read(cx).point_to_buffer_point(point, cx)
             else {
                 continue;
@@ -8818,7 +8819,7 @@ impl Editor {
                 cx,
             );
             for (breakpoint, state) in breakpoints {
-                let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
+                let multi_buffer_anchor = Anchor::text(excerpt_id, breakpoint.position);
                 let position = multi_buffer_anchor
                     .to_point(&multi_buffer_snapshot)
                     .to_display_point(&snapshot);
@@ -24010,7 +24011,7 @@ impl Editor {
                             .for_each(|hint| {
                                 let inlay = Inlay::debugger(
                                     post_inc(&mut editor.next_inlay_id),
-                                    Anchor::in_buffer(excerpt_id, hint.position),
+                                    Anchor::text(excerpt_id, hint.position),
                                     hint.text(),
                                 );
                                 if !inlay.text().chars().contains(&'\n') {
@@ -24113,7 +24114,7 @@ impl Editor {
                     excerpts: excerpts.clone(),
                 });
             }
-            multi_buffer::Event::ExcerptsRemoved {
+            multi_buffer::Event::BuffersRemoved {
                 ids,
                 removed_buffer_ids,
             } => {
@@ -24137,7 +24138,7 @@ impl Editor {
                     removed_buffer_ids: removed_buffer_ids.clone(),
                 });
             }
-            multi_buffer::Event::ExcerptsEdited {
+            multi_buffer::Event::BuffersEdited {
                 excerpt_ids,
                 buffer_ids,
             } => {
@@ -24496,7 +24497,7 @@ impl Editor {
                 line_offset_from_top,
             }) => {
                 let point = MultiBufferPoint::new(row.0, 0);
-                if let Some((buffer, buffer_point, _)) =
+                if let Some((buffer, buffer_point)) =
                     self.buffer.read(cx).point_to_buffer_point(point, cx)
                 {
                     let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);

crates/editor/src/element.rs 🔗

@@ -55,7 +55,7 @@ use itertools::Itertools;
 use language::{IndentGuideSettings, language_settings::ShowWhitespaceSetting};
 use markdown::Markdown;
 use multi_buffer::{
-    Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
+    Anchor, ExcerptBoundaryInfo, ExcerptId, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
     MultiBufferRow, RowInfo,
 };
 
@@ -4183,7 +4183,7 @@ impl EditorElement {
 
     fn render_buffer_header(
         &self,
-        for_excerpt: &ExcerptInfo,
+        for_excerpt: &ExcerptBoundaryInfo,
         is_folded: bool,
         is_selected: bool,
         is_sticky: bool,
@@ -8128,7 +8128,7 @@ pub(crate) fn header_jump_data(
     editor_snapshot: &EditorSnapshot,
     block_row_start: DisplayRow,
     height: u32,
-    first_excerpt: &ExcerptInfo,
+    first_excerpt: &ExcerptBoundaryInfo,
     latest_selection_anchors: &HashMap<BufferId, Anchor>,
 ) -> JumpData {
     let jump_target = if let Some(anchor) = latest_selection_anchors.get(&first_excerpt.buffer_id)
@@ -8195,7 +8195,7 @@ fn header_jump_data_inner(
 
 pub(crate) fn render_buffer_header(
     editor: &Entity<Editor>,
-    for_excerpt: &ExcerptInfo,
+    for_excerpt: &ExcerptBoundaryInfo,
     is_folded: bool,
     is_selected: bool,
     is_sticky: bool,

crates/editor/src/git/blame.rs 🔗

@@ -205,7 +205,7 @@ impl GitBlame {
                     }
                 }
                 multi_buffer::Event::ExcerptsAdded { .. }
-                | multi_buffer::Event::ExcerptsEdited { .. } => git_blame.regenerate_on_edit(cx),
+                | multi_buffer::Event::BuffersEdited { .. } => git_blame.regenerate_on_edit(cx),
                 _ => {}
             },
         );

crates/editor/src/items.rs 🔗

@@ -575,7 +575,7 @@ fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor
 
 fn deserialize_anchor(anchor: proto::EditorAnchor) -> Option<Anchor> {
     let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
-    Some(Anchor::in_buffer(
+    Some(Anchor::text(
         excerpt_id,
         language::proto::deserialize_anchor(anchor.anchor?)?,
     ))
@@ -1459,8 +1459,7 @@ impl ProjectItem for Editor {
                 });
             }
             let (top_row, offset) = restoration_data.scroll_position;
-            let anchor =
-                Anchor::in_buffer(excerpt_id, snapshot.anchor_before(Point::new(top_row, 0)));
+            let anchor = Anchor::text(excerpt_id, snapshot.anchor_before(Point::new(top_row, 0)));
             editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx);
         }
 

crates/git_ui/src/git_panel.rs 🔗

@@ -46,7 +46,7 @@ use language_model::{
     ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
 };
 use menu;
-use multi_buffer::ExcerptInfo;
+use multi_buffer::ExcerptBoundaryInfo;
 use notifications::status_toast::{StatusToast, ToastIcon};
 use panel::{PanelHeader, panel_button, panel_filled_button, panel_icon_button};
 use project::{
@@ -5754,7 +5754,7 @@ impl editor::Addon for GitPanelAddon {
 
     fn render_buffer_header_controls(
         &self,
-        excerpt_info: &ExcerptInfo,
+        excerpt_info: &ExcerptBoundaryInfo,
         window: &Window,
         cx: &App,
     ) -> Option<AnyElement> {

crates/multi_buffer/src/anchor.rs 🔗

@@ -1,195 +1,266 @@
-use crate::{MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
+use crate::{ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
 
-use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
+use super::{MultiBufferSnapshot, ToOffset, ToPoint};
 use language::Point;
 use std::{
     cmp::Ordering,
     ops::{Add, AddAssign, Range, Sub},
 };
 use sum_tree::Bias;
+use text::BufferId;
+use util::debug_panic;
 
 /// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
 ///
 /// Unlike simple offsets, anchors remain valid as the text is edited, automatically
 /// adjusting to reflect insertions and deletions around them.
 #[derive(Clone, Copy, Eq, PartialEq, Hash)]
-pub struct Anchor {
-    /// Identifies which excerpt within the multi-buffer this anchor belongs to.
-    /// A multi-buffer can contain multiple excerpts from different buffers.
-    pub excerpt_id: ExcerptId,
-    /// The position within the excerpt's underlying buffer. This is a stable
-    /// reference that remains valid as the buffer text is edited.
-    pub text_anchor: text::Anchor,
-    /// When present, indicates this anchor points into deleted text within an
-    /// expanded diff hunk. The anchor references a position in the diff base
-    /// (original) text rather than the current buffer text. This is used when
-    /// displaying inline diffs where deleted lines are shown.
-    pub diff_base_anchor: Option<text::Anchor>,
+pub enum Anchor {
+    Min,
+    Max,
+    Text {
+        /// The position within the excerpt's underlying buffer. This is a stable
+        /// reference that remains valid as the buffer text is edited.
+        timestamp: clock::Lamport,
+
+        /// The byte offset into the text inserted in the operation
+        /// at `timestamp`.
+        offset: u32,
+        /// Whether this anchor stays attached to the character *before* or *after*
+        /// the offset.
+        bias: Bias,
+        buffer_id: BufferId,
+        /// When present, indicates this anchor points into deleted text within an
+        /// expanded diff hunk. The anchor references a position in the diff base
+        /// (original) text rather than the current buffer text. This is used when
+        /// displaying inline diffs where deleted lines are shown.
+        diff_base_anchor: Option<text::Anchor>,
+    },
 }
 
 impl std::fmt::Debug for Anchor {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         if self.is_min() {
-            return write!(f, "Anchor::min({:?})", self.text_anchor.buffer_id);
+            return write!(f, "Anchor::Min");
         }
         if self.is_max() {
-            return write!(f, "Anchor::max({:?})", self.text_anchor.buffer_id);
+            return write!(f, "Anchor::Max");
         }
 
         f.debug_struct("Anchor")
-            .field("excerpt_id", &self.excerpt_id)
-            .field("text_anchor", &self.text_anchor)
-            .field("diff_base_anchor", &self.diff_base_anchor)
+            .field("text_anchor", &self.text_anchor().unwrap())
+            .field("diff_base_anchor", &self.diff_base_anchor())
             .finish()
     }
 }
 
 impl Anchor {
-    pub fn with_diff_base_anchor(self, diff_base_anchor: text::Anchor) -> Self {
-        Self {
-            diff_base_anchor: Some(diff_base_anchor),
-            ..self
+    pub fn text_anchor(&self) -> Option<text::Anchor> {
+        match self {
+            Self::Min | Self::Max => None,
+            Self::Text {
+                timestamp,
+                offset,
+                bias,
+                buffer_id,
+                ..
+            } => Some(text::Anchor::new(
+                *timestamp,
+                *offset,
+                *bias,
+                Some(*buffer_id),
+            )),
         }
     }
 
-    pub fn in_buffer(excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Self {
-        Self {
-            excerpt_id,
-            text_anchor,
-            diff_base_anchor: None,
+    pub fn diff_base_anchor(&self) -> Option<text::Anchor> {
+        match self {
+            Self::Min | Self::Max => None,
+            Self::Text {
+                diff_base_anchor, ..
+            } => *diff_base_anchor,
         }
     }
 
-    pub fn range_in_buffer(excerpt_id: ExcerptId, range: Range<text::Anchor>) -> Range<Self> {
-        Self::in_buffer(excerpt_id, range.start)..Self::in_buffer(excerpt_id, range.end)
+    pub fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
+        match &mut self {
+            Self::Min | Self::Max => {
+                debug_panic!("with_diff_base_anchor called on min or max anchor");
+            }
+            Self::Text {
+                diff_base_anchor: self_diff_base_anchor,
+                ..
+            } => {
+                *self_diff_base_anchor = Some(diff_base_anchor);
+            }
+        };
+        self
     }
 
-    pub fn min() -> Self {
-        Self {
-            excerpt_id: ExcerptId::min(),
-            text_anchor: text::Anchor::MIN,
+    pub fn text(text_anchor: text::Anchor) -> Self {
+        let Some(buffer_id) = text_anchor.buffer_id else {
+            panic!("text_anchor must have a buffer_id");
+        };
+        Self::Text {
             diff_base_anchor: None,
+            timestamp: text_anchor.timestamp(),
+            buffer_id,
+            offset: text_anchor.offset,
+            bias: text_anchor.bias,
         }
     }
 
+    pub fn range_in_buffer(range: Range<text::Anchor>) -> Range<Self> {
+        Self::text(range.start)..Self::text(range.end)
+    }
+
+    pub fn min() -> Self {
+        Self::Min
+    }
+
     pub fn max() -> Self {
-        Self {
-            excerpt_id: ExcerptId::max(),
-            text_anchor: text::Anchor::MAX,
-            diff_base_anchor: None,
-        }
+        Self::Max
     }
 
     pub fn is_min(&self) -> bool {
-        self.excerpt_id == ExcerptId::min()
-            && self.text_anchor.is_min()
-            && self.diff_base_anchor.is_none()
+        matches!(self, Self::Min)
     }
 
     pub fn is_max(&self) -> bool {
-        self.excerpt_id == ExcerptId::max()
-            && self.text_anchor.is_max()
-            && self.diff_base_anchor.is_none()
+        matches!(self, Self::Max)
     }
 
     pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
-        if self == other {
-            return Ordering::Equal;
-        }
+        let (self_text_anchor, other_text_anchor) = match (self, other) {
+            (Anchor::Min, Anchor::Min) => return Ordering::Equal,
+            (Anchor::Max, Anchor::Max) => return Ordering::Equal,
+            (Anchor::Min, _) => return Ordering::Less,
+            (Anchor::Max, _) => return Ordering::Greater,
+            (_, Anchor::Max) => return Ordering::Less,
+            (_, Anchor::Min) => return Ordering::Greater,
+            (Anchor::Text { .. }, Anchor::Text { .. }) => {
+                (self.text_anchor().unwrap(), other.text_anchor().unwrap())
+            }
+        };
+        let self_buffer_id = self_text_anchor.buffer_id.unwrap();
+        let other_buffer_id = other_text_anchor.buffer_id.unwrap();
 
-        let self_excerpt_id = snapshot.latest_excerpt_id(self.excerpt_id);
-        let other_excerpt_id = snapshot.latest_excerpt_id(other.excerpt_id);
+        let Some(self_path_key) = snapshot.path_keys.get(&self_buffer_id) else {
+            panic!("path key was never set for buffer_id")
+        };
+        let Some(other_path_key) = snapshot.path_keys.get(&other_buffer_id) else {
+            panic!("path key was never set for buffer_id")
+        };
 
-        let excerpt_id_cmp = self_excerpt_id.cmp(&other_excerpt_id, snapshot);
-        if excerpt_id_cmp.is_ne() {
-            return excerpt_id_cmp;
+        if self_path_key.cmp(other_path_key) != Ordering::Equal {
+            return self_path_key.cmp(other_path_key);
         }
-        if self_excerpt_id == ExcerptId::max()
-            && self.text_anchor.is_max()
-            && self.text_anchor.is_max()
-            && self.diff_base_anchor.is_none()
-            && other.diff_base_anchor.is_none()
-        {
+
+        // in the case that you removed the buffer contianing self,
+        // and added the buffer containing other with the same path key
+        if self_buffer_id != other_buffer_id {
+            return self_buffer_id.cmp(&other_buffer_id);
+        }
+
+        let Some(buffer) = snapshot.buffer_for_path(&self_path_key) else {
             return Ordering::Equal;
+        };
+        let text_cmp = self_text_anchor.cmp(&other_text_anchor, buffer);
+        if text_cmp != Ordering::Equal {
+            return text_cmp;
         }
-        if let Some(excerpt) = snapshot.excerpt(self_excerpt_id) {
-            let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
-            if text_cmp.is_ne() {
-                return text_cmp;
-            }
-            if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
-                && let Some(base_text) = snapshot
-                    .diffs
-                    .get(&excerpt.buffer_id)
-                    .map(|diff| diff.base_text())
-            {
-                let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
-                let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
-                return match (self_anchor, other_anchor) {
-                    (Some(a), Some(b)) => a.cmp(&b, base_text),
-                    (Some(_), None) => match other.text_anchor.bias {
-                        Bias::Left => Ordering::Greater,
-                        Bias::Right => Ordering::Less,
-                    },
-                    (None, Some(_)) => match self.text_anchor.bias {
-                        Bias::Left => Ordering::Less,
-                        Bias::Right => Ordering::Greater,
-                    },
-                    (None, None) => Ordering::Equal,
-                };
-            }
+
+        if (self.diff_base_anchor().is_some() || other.diff_base_anchor().is_some())
+            && let Some(base_text) = snapshot
+                .diffs
+                .get(&self_buffer_id)
+                .map(|diff| diff.base_text())
+        {
+            let self_anchor = self.diff_base_anchor().filter(|a| a.is_valid(base_text));
+            let other_anchor = other.diff_base_anchor().filter(|a| a.is_valid(base_text));
+            return match (self_anchor, other_anchor) {
+                (Some(a), Some(b)) => a.cmp(&b, base_text),
+                (Some(_), None) => match other_text_anchor.bias {
+                    Bias::Left => Ordering::Greater,
+                    Bias::Right => Ordering::Less,
+                },
+                (None, Some(_)) => match self_text_anchor.bias {
+                    Bias::Left => Ordering::Less,
+                    Bias::Right => Ordering::Greater,
+                },
+                (None, None) => Ordering::Equal,
+            };
         }
+
         Ordering::Equal
     }
 
     pub fn bias(&self) -> Bias {
-        self.text_anchor.bias
+        match self {
+            Anchor::Min => Bias::Left,
+            Anchor::Max => Bias::Right,
+            Anchor::Text { bias, .. } => *bias,
+        }
     }
 
     pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
-        if self.text_anchor.bias != Bias::Left
-            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
-        {
-            return Self {
-                excerpt_id: excerpt.id,
-                text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
-                diff_base_anchor: self.diff_base_anchor.map(|a| {
-                    if let Some(base_text) = snapshot
-                        .diffs
-                        .get(&excerpt.buffer_id)
-                        .map(|diff| diff.base_text())
-                        && a.is_valid(&base_text)
+        match self {
+            Anchor::Min => *self,
+            Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
+            Anchor::Text {
+                bias, buffer_id, ..
+            } => {
+                if *bias == Bias::Left {
+                    return *self;
+                }
+                let Some(buffer) = snapshot.buffer_for_id(*buffer_id) else {
+                    return *self;
+                };
+                let text_anchor = self.text_anchor().unwrap().bias_left(&buffer);
+                let ret = Self::text(text_anchor);
+                if let Some(diff_base_anchor) = self.diff_base_anchor() {
+                    if let Some(diff) = snapshot.diffs.get(&buffer_id)
+                        && diff_base_anchor.is_valid(&diff.base_text())
                     {
-                        return a.bias_left(base_text);
+                        ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text()))
+                    } else {
+                        ret.with_diff_base_anchor(diff_base_anchor)
                     }
-                    a
-                }),
-            };
+                } else {
+                    ret
+                }
+            }
         }
-        *self
     }
 
     pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
-        if self.text_anchor.bias != Bias::Right
-            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
-        {
-            return Self {
-                excerpt_id: excerpt.id,
-                text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
-                diff_base_anchor: self.diff_base_anchor.map(|a| {
-                    if let Some(base_text) = snapshot
-                        .diffs
-                        .get(&excerpt.buffer_id)
-                        .map(|diff| diff.base_text())
-                        && a.is_valid(&base_text)
+        match self {
+            Anchor::Min => *self,
+            Anchor::Max => snapshot.anchor_after(Point::zero()),
+            Anchor::Text {
+                bias, buffer_id, ..
+            } => {
+                if *bias == Bias::Right {
+                    return *self;
+                }
+                let Some(buffer) = snapshot.buffer_for_id(*buffer_id) else {
+                    return *self;
+                };
+                let text_anchor = self.text_anchor().unwrap().bias_right(&buffer);
+                let ret = Self::text(text_anchor);
+                if let Some(diff_base_anchor) = self.diff_base_anchor() {
+                    if let Some(diff) = snapshot.diffs.get(&buffer_id)
+                        && diff_base_anchor.is_valid(&diff.base_text())
                     {
-                        return a.bias_right(base_text);
+                        ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text()))
+                    } else {
+                        ret.with_diff_base_anchor(diff_base_anchor)
                     }
-                    a
-                }),
-            };
+                } else {
+                    ret
+                }
+            }
         }
-        *self
     }
 
     pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
@@ -206,16 +277,35 @@ impl Anchor {
     }
 
     pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
-        if self.is_min() || self.is_max() {
-            true
-        } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
-            (self.text_anchor == excerpt.range.context.start
-                || self.text_anchor == excerpt.range.context.end
-                || self.text_anchor.is_valid(&excerpt.buffer))
-                && excerpt.contains(self)
-        } else {
-            false
-        }
+        let Some(text_anchor) = self.text_anchor() else {
+            return true;
+        };
+        let Some(buffer_id) = text_anchor.buffer_id else {
+            debug_panic!("missing buffer_id for anchor");
+            return false;
+        };
+
+        let Some(target) = snapshot.anchor_seek_target(*self) else {
+            return false;
+        };
+        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
+        cursor.seek(&target, Bias::Left);
+        let Some(excerpt) = cursor.item() else {
+            return false;
+        };
+        excerpt.buffer.remote_id() == buffer_id
+            && excerpt
+                .range
+                .context
+                .start
+                .cmp(&text_anchor, &excerpt.buffer)
+                .is_le()
+            && excerpt
+                .range
+                .context
+                .end
+                .cmp(&text_anchor, &excerpt.buffer)
+                .is_ge()
     }
 }
 

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -15,7 +15,7 @@ use buffer_diff::{
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet};
-use gpui::{App, Context, Entity, EntityId, EventEmitter};
+use gpui::{App, Context, Entity, EventEmitter};
 use itertools::Itertools;
 use language::{
     AutoindentMode, BracketMatch, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability,
@@ -36,7 +36,8 @@ use std::{
     any::type_name,
     borrow::Cow,
     cell::{Cell, OnceCell, Ref, RefCell},
-    cmp, fmt,
+    cmp::{self, Ordering},
+    fmt,
     future::Future,
     io,
     iter::{self, FromIterator},
@@ -54,7 +55,6 @@ use text::{
     subscription::{Subscription, Topic},
 };
 use theme::SyntaxTheme;
-use util::post_inc;
 use ztracing::instrument;
 
 pub use self::path_key::{PathExcerptInsertResult, PathKey};
@@ -65,9 +65,6 @@ pub fn excerpt_context_lines(cx: &App) -> u32 {
     EXCERPT_CONTEXT_LINES.get().map(|f| f(cx)).unwrap_or(2)
 }
 
-#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ExcerptId(u32);
-
 /// One or more [`Buffers`](Buffer) being edited in a single view.
 ///
 /// See <https://zed.dev/features#multi-buffers>
@@ -77,10 +74,6 @@ pub struct MultiBuffer {
     snapshot: RefCell<MultiBufferSnapshot>,
     /// Contains the state of the buffers being edited
     buffers: BTreeMap<BufferId, BufferState>,
-    /// Mapping from path keys to their excerpts.
-    excerpts_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
-    /// Mapping from excerpt IDs to their path key.
-    paths_by_excerpt: HashMap<ExcerptId, PathKey>,
     /// Mapping from buffer IDs to their diff states
     diffs: HashMap<BufferId, DiffState>,
     subscriptions: Topic<MultiBufferOffset>,
@@ -96,22 +89,23 @@ pub struct MultiBuffer {
     buffer_changed_since_sync: Rc<Cell<bool>>,
 }
 
+pub struct ExcerptInfo {
+    path_key: PathKey,
+    buffer_id: BufferId,
+    range: ExcerptRange<text::Anchor>,
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Event {
-    ExcerptsAdded {
+    BufferUpdated {
         buffer: Entity<Buffer>,
-        predecessor: ExcerptId,
-        excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
+        path_key: PathKey,
+        ranges: Vec<Range<text::Anchor>>,
     },
-    ExcerptsRemoved {
-        ids: Vec<ExcerptId>,
+    BuffersRemoved {
         removed_buffer_ids: Vec<BufferId>,
     },
-    ExcerptsExpanded {
-        ids: Vec<ExcerptId>,
-    },
-    ExcerptsEdited {
-        excerpt_ids: Vec<ExcerptId>,
+    BuffersEdited {
         buffer_ids: Vec<BufferId>,
     },
     DiffHunksToggled,
@@ -140,8 +134,6 @@ pub struct MultiBufferDiffHunk {
     pub buffer_id: BufferId,
     /// The range of the underlying buffer that this hunk corresponds to.
     pub buffer_range: Range<text::Anchor>,
-    /// The excerpt that contains the diff hunk.
-    pub excerpt_id: ExcerptId,
     /// The range within the buffer's diff base that this hunk corresponds to.
     pub diff_base_byte_range: Range<BufferOffset>,
     /// The status of this hunk (added/modified/deleted and secondary status).
@@ -162,8 +154,8 @@ impl MultiBufferDiffHunk {
     }
 
     pub fn multi_buffer_range(&self) -> Range<Anchor> {
-        let start = Anchor::in_buffer(self.excerpt_id, self.buffer_range.start);
-        let end = Anchor::in_buffer(self.excerpt_id, self.buffer_range.end);
+        let start = Anchor::text(self.buffer_range.start);
+        let end = Anchor::text(self.buffer_range.end);
         start..end
     }
 }
@@ -513,10 +505,9 @@ pub trait ToPoint: 'static + fmt::Debug {
 
 struct BufferState {
     buffer: Entity<Buffer>,
+    path_key: PathKey,
     last_version: RefCell<clock::Global>,
     last_non_text_state_update_count: Cell<usize>,
-    // Note, any changes to this field value require updating snapshot.buffer_locators as well
-    excerpts: Vec<Locator>,
     _subscriptions: [gpui::Subscription; 2],
 }
 
@@ -620,11 +611,9 @@ impl DiffState {
 #[derive(Clone, Default)]
 pub struct MultiBufferSnapshot {
     excerpts: SumTree<Excerpt>,
-    buffer_locators: TreeMap<BufferId, Arc<[Locator]>>,
+    path_keys: TreeMap<BufferId, PathKey>,
     diffs: TreeMap<BufferId, DiffStateSnapshot>,
     diff_transforms: SumTree<DiffTransform>,
-    excerpt_ids: SumTree<ExcerptIdMapping>,
-    replaced_excerpts: Arc<HashMap<ExcerptId, ExcerptId>>,
     non_text_state_update_count: usize,
     edit_count: usize,
     is_dirty: bool,
@@ -639,24 +628,12 @@ pub struct MultiBufferSnapshot {
     show_headers: bool,
 }
 
-// follower: None
-// - BufferContent(Some)
-// - BufferContent(None)
-// - DeletedHunk
-//
-// follower: Some
-// - BufferContent(Some)
-// - BufferContent(None)
-
 #[derive(Debug, Clone)]
 enum DiffTransform {
-    // RealText
     BufferContent {
         summary: MBTextSummary,
-        // modified_hunk_info
         inserted_hunk_info: Option<DiffTransformHunkInfo>,
     },
-    // ExpandedHunkText
     DeletedHunk {
         summary: TextSummary,
         buffer_id: BufferId,
@@ -668,7 +645,7 @@ enum DiffTransform {
 
 #[derive(Clone, Copy, Debug)]
 struct DiffTransformHunkInfo {
-    excerpt_id: ExcerptId,
+    buffer_id: BufferId,
     hunk_start_anchor: text::Anchor,
     hunk_secondary_status: DiffHunkSecondaryStatus,
     is_logically_deleted: bool,
@@ -678,30 +655,28 @@ impl Eq for DiffTransformHunkInfo {}
 
 impl PartialEq for DiffTransformHunkInfo {
     fn eq(&self, other: &DiffTransformHunkInfo) -> bool {
-        self.excerpt_id == other.excerpt_id && self.hunk_start_anchor == other.hunk_start_anchor
+        self.buffer_id == other.buffer_id && self.hunk_start_anchor == other.hunk_start_anchor
     }
 }
 
 impl std::hash::Hash for DiffTransformHunkInfo {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.excerpt_id.hash(state);
+        self.buffer_id.hash(state);
         self.hunk_start_anchor.hash(state);
     }
 }
 
 #[derive(Clone)]
-pub struct ExcerptInfo {
-    pub id: ExcerptId,
+pub struct ExcerptBoundaryInfo {
     pub buffer: Arc<BufferSnapshot>,
     pub buffer_id: BufferId,
     pub range: ExcerptRange<text::Anchor>,
     pub end_row: MultiBufferRow,
 }
 
-impl std::fmt::Debug for ExcerptInfo {
+impl std::fmt::Debug for ExcerptBoundaryInfo {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct(type_name::<Self>())
-            .field("id", &self.id)
             .field("buffer_id", &self.buffer_id)
             .field("path", &self.buffer.file().map(|f| f.path()))
             .field("range", &self.range)
@@ -712,8 +687,8 @@ impl std::fmt::Debug for ExcerptInfo {
 /// A boundary between `Excerpt`s in a [`MultiBuffer`]
 #[derive(Debug)]
 pub struct ExcerptBoundary {
-    pub prev: Option<ExcerptInfo>,
-    pub next: ExcerptInfo,
+    pub prev: Option<ExcerptBoundaryInfo>,
+    pub next: ExcerptBoundaryInfo,
     /// The row in the `MultiBuffer` where the boundary is located
     pub row: MultiBufferRow,
 }
@@ -727,13 +702,13 @@ impl ExcerptBoundary {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub struct ExpandInfo {
     pub direction: ExpandExcerptDirection,
-    pub excerpt_id: ExcerptId,
+    pub excerpt_range: Range<text::Anchor>,
 }
 
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
 pub struct RowInfo {
     pub buffer_id: Option<BufferId>,
     pub buffer_row: Option<u32>,
@@ -745,22 +720,19 @@ pub struct RowInfo {
 
 /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
 #[derive(Clone)]
-struct Excerpt {
-    /// The unique identifier for this excerpt
-    id: ExcerptId,
+pub(crate) struct Excerpt {
     /// The location of the excerpt in the [`MultiBuffer`]
-    locator: Locator,
-    /// The buffer being excerpted
-    buffer_id: BufferId,
+    pub(crate) path_key: PathKey,
     /// A snapshot of the buffer being excerpted
-    buffer: Arc<BufferSnapshot>,
+    pub(crate) buffer: Arc<BufferSnapshot>,
     /// The range of the buffer to be shown in the excerpt
-    range: ExcerptRange<text::Anchor>,
+    pub(crate) range: ExcerptRange<text::Anchor>,
+
     /// The last row in the excerpted slice of the buffer
-    max_buffer_row: BufferRow,
+    pub(crate) max_buffer_row: BufferRow,
     /// A summary of the text in the excerpt
-    text_summary: TextSummary,
-    has_trailing_newline: bool,
+    pub(crate) text_summary: TextSummary,
+    pub(crate) has_trailing_newline: bool,
 }
 
 /// A public view into an `Excerpt` in a [`MultiBuffer`].
@@ -779,12 +751,6 @@ pub struct MultiBufferExcerpt<'a> {
     buffer_offset: BufferOffset,
 }
 
-#[derive(Clone, Debug)]
-struct ExcerptIdMapping {
-    id: ExcerptId,
-    locator: Locator,
-}
-
 /// A range of text from a single [`Buffer`], to be shown as an `Excerpt`.
 /// These ranges are relative to the buffer itself
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
@@ -805,16 +771,34 @@ impl<T: Clone> ExcerptRange<T> {
     }
 }
 
-#[derive(Clone, Debug, Default)]
+impl ExcerptRange<text::Anchor> {
+    pub fn contains(&self, t: &text::Anchor, snapshot: &BufferSnapshot) -> bool {
+        self.context.start.cmp(t, snapshot).is_le() && self.context.end.cmp(t, snapshot).is_ge()
+    }
+}
+
+#[derive(Clone, Debug)]
 pub struct ExcerptSummary {
-    excerpt_id: ExcerptId,
-    /// The location of the last [`Excerpt`] being summarized
-    excerpt_locator: Locator,
+    path_key: PathKey,
+    /// End of the excerpt
+    max_anchor: text::Anchor,
     widest_line_number: u32,
     text: MBTextSummary,
     count: usize,
 }
 
+impl ExcerptSummary {
+    pub fn min() -> Self {
+        ExcerptSummary {
+            path_key: PathKey::min(),
+            max_anchor: text::Anchor::MIN,
+            widest_line_number: 0,
+            text: MBTextSummary::default(),
+            count: 0,
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct DiffTransformSummary {
     input: MBTextSummary,
@@ -1066,7 +1050,6 @@ struct MultiBufferRegion<'a, MBD, BD> {
 }
 
 struct ExcerptChunks<'a> {
-    excerpt_id: ExcerptId,
     content_chunks: BufferChunks<'a>,
     has_footer: bool,
 }
@@ -1077,7 +1060,6 @@ struct BufferEdit {
     new_text: Arc<str>,
     is_insertion: bool,
     original_indent_column: Option<u32>,
-    excerpt_id: ExcerptId,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq)]
@@ -1180,8 +1162,6 @@ impl MultiBuffer {
             singleton: false,
             capability,
             title: None,
-            excerpts_by_path: Default::default(),
-            paths_by_excerpt: Default::default(),
             buffer_changed_since_sync: Default::default(),
             history: History::default(),
         }
@@ -1198,11 +1178,11 @@ impl MultiBuffer {
                 *buffer_id,
                 BufferState {
                     buffer: buffer_state.buffer.clone(),
+                    path_key: buffer_state.path_key.clone(),
                     last_version: buffer_state.last_version.clone(),
                     last_non_text_state_update_count: buffer_state
                         .last_non_text_state_update_count
                         .clone(),
-                    excerpts: buffer_state.excerpts.clone(),
                     _subscriptions: [
                         new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()),
                         new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event),
@@ -1217,8 +1197,6 @@ impl MultiBuffer {
         Self {
             snapshot: RefCell::new(self.snapshot.borrow().clone()),
             buffers,
-            excerpts_by_path: Default::default(),
-            paths_by_excerpt: Default::default(),
             diffs: diff_bases,
             subscriptions: Default::default(),
             singleton: self.singleton,
@@ -1366,7 +1344,7 @@ impl MultiBuffer {
                 _ => Default::default(),
             };
 
-            let (buffer_edits, edited_excerpt_ids) = MultiBuffer::convert_edits_to_buffer_edits(
+            let buffer_edits = MultiBuffer::convert_edits_to_buffer_edits(
                 edits,
                 this.snapshot.get_mut(),
                 &original_indent_columns,
@@ -1387,14 +1365,12 @@ impl MultiBuffer {
                         mut new_text,
                         mut is_insertion,
                         original_indent_column,
-                        excerpt_id,
                     }) = edits.next()
                     {
                         while let Some(BufferEdit {
                             range: next_range,
                             is_insertion: next_is_insertion,
                             new_text: next_new_text,
-                            excerpt_id: next_excerpt_id,
                             ..
                         }) = edits.peek()
                         {
@@ -1407,9 +1383,7 @@ impl MultiBuffer {
                             if should_coalesce {
                                 range.end = cmp::max(next_range.end, range.end);
                                 is_insertion |= *next_is_insertion;
-                                if excerpt_id == *next_excerpt_id {
-                                    new_text = format!("{new_text}{next_new_text}").into();
-                                }
+                                new_text = format!("{new_text}{next_new_text}").into();
                                 edits.next();
                             } else {
                                 break;
@@ -1457,10 +1431,7 @@ impl MultiBuffer {
                 })
             }
 
-            cx.emit(Event::ExcerptsEdited {
-                excerpt_ids: edited_excerpt_ids,
-                buffer_ids,
-            });
+            cx.emit(Event::BuffersEdited { buffer_ids });
         }
     }
 
@@ -1468,9 +1439,8 @@ impl MultiBuffer {
         edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
         snapshot: &MultiBufferSnapshot,
         original_indent_columns: &[Option<u32>],
-    ) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
+    ) -> HashMap<BufferId, Vec<BufferEdit>> {
         let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
-        let mut edited_excerpt_ids = Vec::new();
         let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
         for (ix, (range, new_text)) in edits.into_iter().enumerate() {
             let original_indent_column = original_indent_columns.get(ix).copied().flatten();
@@ -1515,11 +1485,10 @@ impl MultiBuffer {
             let buffer_end =
                 (end_region.buffer_range.start + end_overshoot).min(end_region.buffer_range.end);
 
-            if start_region.excerpt.id == end_region.excerpt.id {
+            if start_region.excerpt == end_region.excerpt {
                 if start_region.buffer.capability == Capability::ReadWrite
                     && start_region.is_main_buffer
                 {
-                    edited_excerpt_ids.push(start_region.excerpt.id);
                     buffer_edits
                         .entry(start_region.buffer.remote_id())
                         .or_default()
@@ -1528,7 +1497,6 @@ impl MultiBuffer {
                             new_text,
                             is_insertion: true,
                             original_indent_column,
-                            excerpt_id: start_region.excerpt.id,
                         });
                 }
             } else {
@@ -1537,7 +1505,6 @@ impl MultiBuffer {
                 if start_region.buffer.capability == Capability::ReadWrite
                     && start_region.is_main_buffer
                 {
-                    edited_excerpt_ids.push(start_region.excerpt.id);
                     buffer_edits
                         .entry(start_region.buffer.remote_id())
                         .or_default()
@@ -1546,14 +1513,11 @@ impl MultiBuffer {
                             new_text: new_text.clone(),
                             is_insertion: true,
                             original_indent_column,
-                            excerpt_id: start_region.excerpt.id,
                         });
                 }
-                let excerpt_id = end_region.excerpt.id;
                 if end_region.buffer.capability == Capability::ReadWrite
                     && end_region.is_main_buffer
                 {
-                    edited_excerpt_ids.push(excerpt_id);
                     buffer_edits
                         .entry(end_region.buffer.remote_id())
                         .or_default()
@@ -1562,18 +1526,17 @@ impl MultiBuffer {
                             new_text: new_text.clone(),
                             is_insertion: false,
                             original_indent_column,
-                            excerpt_id,
                         });
                 }
+                let end_region_excerpt = end_region.excerpt.clone();
 
                 cursor.seek(&range.start);
                 cursor.next_excerpt();
                 while let Some(region) = cursor.region() {
-                    if region.excerpt.id == excerpt_id {
+                    if region.excerpt == &end_region_excerpt {
                         break;
                     }
                     if region.buffer.capability == Capability::ReadWrite && region.is_main_buffer {
-                        edited_excerpt_ids.push(region.excerpt.id);
                         buffer_edits
                             .entry(region.buffer.remote_id())
                             .or_default()
@@ -1582,14 +1545,13 @@ impl MultiBuffer {
                                 new_text: new_text.clone(),
                                 is_insertion: false,
                                 original_indent_column,
-                                excerpt_id: region.excerpt.id,
                             });
                     }
                     cursor.next_excerpt();
                 }
             }
         }
-        (buffer_edits, edited_excerpt_ids)
+        buffer_edits
     }
 
     pub fn autoindent_ranges<I, S>(&mut self, ranges: I, cx: &mut Context<Self>)
@@ -1621,7 +1583,7 @@ impl MultiBuffer {
             edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
             cx: &mut Context<MultiBuffer>,
         ) {
-            let (buffer_edits, edited_excerpt_ids) =
+            let buffer_edits =
                 MultiBuffer::convert_edits_to_buffer_edits(edits, this.snapshot.get_mut(), &[]);
 
             let mut buffer_ids = Vec::new();
@@ -1645,10 +1607,7 @@ impl MultiBuffer {
                 })
             }
 
-            cx.emit(Event::ExcerptsEdited {
-                excerpt_ids: edited_excerpt_ids,
-                buffer_ids,
-            });
+            cx.emit(Event::BuffersEdited { buffer_ids });
         }
     }
 
@@ -1662,25 +1621,33 @@ impl MultiBuffer {
         let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
             Default::default();
         let snapshot = self.read(cx);
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
+        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
         for selection in selections {
-            let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
-            let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
+            let Some(start) = snapshot.anchor_seek_target(selection.start) else {
+                continue;
+            };
+            let Some(end) = snapshot.anchor_seek_target(selection.end) else {
+                continue;
+            };
 
-            cursor.seek(&Some(start_locator), Bias::Left);
+            cursor.seek(&start, Bias::Left);
             while let Some(excerpt) = cursor.item()
-                && excerpt.locator <= *end_locator
+                && sum_tree::SeekTarget::cmp(&end, &cursor.position, ()).is_gt()
             {
-                let mut start = excerpt.range.context.start;
-                let mut end = excerpt.range.context.end;
-                if excerpt.id == selection.start.excerpt_id {
-                    start = selection.start.text_anchor;
-                }
-                if excerpt.id == selection.end.excerpt_id {
-                    end = selection.end.text_anchor;
-                }
+                let start = text::Anchor::max(
+                    &excerpt.range.context.start,
+                    &selection.start.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),
+                    &excerpt.buffer,
+                )
+                .clone();
                 selections_by_buffer
-                    .entry(excerpt.buffer_id)
+                    .entry(excerpt.buffer.remote_id())
                     .or_default()
                     .push(Selection {
                         id: selection.id,
@@ -1702,25 +1669,9 @@ impl MultiBuffer {
             }
         }
 
-        for (buffer_id, mut selections) in selections_by_buffer {
+        for (buffer_id, selections) in selections_by_buffer {
             self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
-                selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
-                let mut selections = selections.into_iter().peekable();
-                let merged_selections = Arc::from_iter(iter::from_fn(|| {
-                    let mut selection = selections.next()?;
-                    while let Some(next_selection) = selections.peek() {
-                        if selection.end.cmp(&next_selection.start, buffer).is_ge() {
-                            let next_selection = selections.next().unwrap();
-                            if next_selection.end.cmp(&selection.end, buffer).is_ge() {
-                                selection.end = next_selection.end;
-                            }
-                        } else {
-                            break;
-                        }
-                    }
-                    Some(selection)
-                }));
-                buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
+                buffer.set_active_selections(selections.into(), line_mode, cursor_shape, cx);
             });
         }
     }
@@ -1759,175 +1710,11 @@ impl MultiBuffer {
         (merged_ranges, counts)
     }
 
-    pub fn insert_excerpts_after<O>(
-        &mut self,
-        prev_excerpt_id: ExcerptId,
-        buffer: Entity<Buffer>,
-        ranges: impl IntoIterator<Item = ExcerptRange<O>>,
-        cx: &mut Context<Self>,
-    ) -> Vec<ExcerptId>
-    where
-        O: text::ToOffset,
-    {
-        let mut ids = Vec::new();
-        let mut next_excerpt_id =
-            if let Some(last_entry) = self.snapshot.borrow().excerpt_ids.last() {
-                last_entry.id.0 + 1
-            } else {
-                1
-            };
-        self.insert_excerpts_with_ids_after(
-            prev_excerpt_id,
-            buffer,
-            ranges.into_iter().map(|range| {
-                let id = ExcerptId(post_inc(&mut next_excerpt_id));
-                ids.push(id);
-                (id, range)
-            }),
-            cx,
-        );
-        ids
-    }
-
-    pub fn insert_excerpts_with_ids_after<O>(
-        &mut self,
-        prev_excerpt_id: ExcerptId,
-        buffer: Entity<Buffer>,
-        ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
-        cx: &mut Context<Self>,
-    ) where
-        O: text::ToOffset,
-    {
-        assert_eq!(self.history.transaction_depth(), 0);
-        let mut ranges = ranges.into_iter().peekable();
-        if ranges.peek().is_none() {
-            return Default::default();
-        }
-
-        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.buffer_changed_since_sync.replace(true);
-            buffer.update(cx, |buffer, _| {
-                buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
-            });
-            BufferState {
-                last_version: RefCell::new(buffer_snapshot.version().clone()),
-                last_non_text_state_update_count: Cell::new(
-                    buffer_snapshot.non_text_state_update_count(),
-                ),
-                excerpts: Default::default(),
-                _subscriptions: [
-                    cx.observe(&buffer, |_, _, cx| cx.notify()),
-                    cx.subscribe(&buffer, Self::on_buffer_event),
-                ],
-                buffer: buffer.clone(),
-            }
-        });
-
-        let mut snapshot = self.snapshot.get_mut();
-
-        let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
-        let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
-        let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right);
-        prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
-
-        let edit_start = ExcerptDimension(new_excerpts.summary().text.len);
-        new_excerpts.update_last(
-            |excerpt| {
-                excerpt.has_trailing_newline = true;
-            },
-            (),
-        );
-
-        let next_locator = if let Some(excerpt) = cursor.item() {
-            excerpt.locator.clone()
-        } else {
-            Locator::max()
-        };
-
-        let mut excerpts = Vec::new();
-        let buffer_snapshot = Arc::new(buffer_snapshot);
-        while let Some((id, range)) = ranges.next() {
-            let locator = Locator::between(&prev_locator, &next_locator);
-            if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
-                buffer_state.excerpts.insert(ix, locator.clone());
-            }
-            let range = ExcerptRange {
-                context: buffer_snapshot.anchor_before(&range.context.start)
-                    ..buffer_snapshot.anchor_after(&range.context.end),
-                primary: buffer_snapshot.anchor_before(&range.primary.start)
-                    ..buffer_snapshot.anchor_after(&range.primary.end),
-            };
-            excerpts.push((id, range.clone()));
-            let excerpt = Excerpt::new(
-                id,
-                locator.clone(),
-                buffer_id,
-                buffer_snapshot.clone(),
-                range,
-                ranges.peek().is_some() || cursor.item().is_some(),
-            );
-            new_excerpts.push(excerpt, ());
-            prev_locator = locator.clone();
-
-            if let Some(last_mapping_entry) = new_excerpt_ids.last() {
-                assert!(id > last_mapping_entry.id, "excerpt ids must be increasing");
-            }
-            new_excerpt_ids.push(ExcerptIdMapping { id, locator }, ());
-        }
-        snapshot
-            .buffer_locators
-            .insert(buffer_id, buffer_state.excerpts.iter().cloned().collect());
-
-        let edit_end = ExcerptDimension(new_excerpts.summary().text.len);
-
-        let suffix = cursor.suffix();
-        let changed_trailing_excerpt = suffix.is_empty();
-        new_excerpts.append(suffix, ());
-        drop(cursor);
-        snapshot.excerpts = new_excerpts;
-        snapshot.excerpt_ids = new_excerpt_ids;
-        if changed_trailing_excerpt {
-            snapshot.trailing_excerpt_update_count += 1;
-        }
-
-        let edits = Self::sync_diff_transforms(
-            &mut snapshot,
-            vec![Edit {
-                old: edit_start..edit_start,
-                new: edit_start..edit_end,
-            }],
-            DiffChangeKind::BufferEdited,
-        );
-        if !edits.is_empty() {
-            self.subscriptions.publish(edits);
-        }
-
-        cx.emit(Event::Edited {
-            edited_buffer: None,
-        });
-        cx.emit(Event::ExcerptsAdded {
-            buffer,
-            predecessor: prev_excerpt_id,
-            excerpts,
-        });
-        cx.notify();
-    }
-
     pub fn clear(&mut self, cx: &mut Context<Self>) {
         self.sync_mut(cx);
-        let ids = self.excerpt_ids();
         let removed_buffer_ids = std::mem::take(&mut self.buffers).into_keys().collect();
-        self.excerpts_by_path.clear();
-        self.paths_by_excerpt.clear();
         let MultiBufferSnapshot {
             excerpts,
-            buffer_locators,
             diffs: _,
             diff_transforms: _,
             non_text_state_update_count: _,
@@ -1937,15 +1724,13 @@ impl MultiBuffer {
             has_conflict,
             has_inverted_diff,
             singleton: _,
-            excerpt_ids: _,
-            replaced_excerpts,
             trailing_excerpt_update_count,
             all_diff_hunks_expanded: _,
             show_deleted_hunks: _,
             use_extended_diff_range: _,
             show_headers: _,
+            path_keys: _,
         } = self.snapshot.get_mut();
-        buffer_locators.clear();
         let start = ExcerptDimension(MultiBufferOffset::ZERO);
         let prev_len = ExcerptDimension(excerpts.summary().text.len);
         *excerpts = Default::default();
@@ -1954,10 +1739,6 @@ impl MultiBuffer {
         *has_deleted_file = false;
         *has_conflict = false;
         *has_inverted_diff = false;
-        match Arc::get_mut(replaced_excerpts) {
-            Some(replaced_excerpts) => replaced_excerpts.clear(),
-            None => *replaced_excerpts = Default::default(),
-        }
 
         let edits = Self::sync_diff_transforms(
             self.snapshot.get_mut(),
@@ -1973,10 +1754,7 @@ impl MultiBuffer {
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
-        cx.emit(Event::ExcerptsRemoved {
-            ids,
-            removed_buffer_ids,
-        });
+        cx.emit(Event::BuffersRemoved { removed_buffer_ids });
         cx.notify();
     }
 
@@ -1985,21 +1763,22 @@ impl MultiBuffer {
         &self,
         buffer_id: BufferId,
         cx: &App,
-    ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
+    ) -> Vec<ExcerptRange<text::Anchor>> {
         let mut excerpts = Vec::new();
         let snapshot = self.read(cx);
-        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
-        if let Some(locators) = snapshot.buffer_locators.get(&buffer_id) {
-            for locator in &**locators {
-                cursor.seek_forward(&Some(locator), Bias::Left);
-                if let Some(excerpt) = cursor.item()
-                    && excerpt.locator == *locator
-                {
-                    excerpts.push((excerpt.id, excerpt.range.clone()));
-                }
-            }
-        }
+        let Some(path_key) = snapshot.path_keys.get(&buffer_id).cloned() else {
+            return excerpts;
+        };
+
+        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
+        {
+            excerpts.push(item.range.clone());
+            cursor.next()
+        }
         excerpts
     }
 
@@ -2007,84 +1786,67 @@ impl MultiBuffer {
         let snapshot = self.read(cx);
         let mut excerpts = snapshot
             .excerpts
-            .cursor::<Dimensions<Option<&Locator>, ExcerptPoint>>(());
+            .cursor::<Dimensions<PathKey, ExcerptPoint>>(());
         let mut diff_transforms = snapshot
             .diff_transforms
             .cursor::<Dimensions<ExcerptPoint, OutputDimension<Point>>>(());
         diff_transforms.next();
-        let locators = snapshot
-            .buffer_locators
-            .get(&buffer_id)
-            .into_iter()
-            .flat_map(|v| &**v);
         let mut result = Vec::new();
-        for locator in locators {
-            excerpts.seek_forward(&Some(locator), Bias::Left);
-            if let Some(excerpt) = excerpts.item()
-                && excerpt.locator == *locator
-            {
-                let excerpt_start = excerpts.start().1;
-                let excerpt_end = excerpt_start + excerpt.text_summary.lines;
+        let Some(path_key) = snapshot.path_keys.get(&buffer_id) else {
+            return result;
+        };
 
-                diff_transforms.seek_forward(&excerpt_start, Bias::Left);
-                let overshoot = excerpt_start - diff_transforms.start().0;
-                let start = diff_transforms.start().1 + overshoot;
+        while let Some(excerpt) = excerpts.item()
+            && &excerpt.path_key == path_key
+        {
+            let excerpt_start = excerpts.start().1;
+            let excerpt_end = excerpt_start + excerpt.text_summary.lines;
 
-                diff_transforms.seek_forward(&excerpt_end, Bias::Right);
-                let overshoot = excerpt_end - diff_transforms.start().0;
-                let end = diff_transforms.start().1 + overshoot;
+            diff_transforms.seek_forward(&excerpt_start, Bias::Left);
+            let overshoot = excerpt_start - diff_transforms.start().0;
+            let start = diff_transforms.start().1 + overshoot;
 
-                result.push(start.0..end.0)
-            }
+            diff_transforms.seek_forward(&excerpt_end, Bias::Right);
+            let overshoot = excerpt_end - diff_transforms.start().0;
+            let end = diff_transforms.start().1 + overshoot;
+
+            result.push(start.0..end.0);
+            excerpts.next()
         }
         result
     }
 
+    // todo!() this seems bogus
     pub fn excerpt_buffer_ids(&self) -> Vec<BufferId> {
         self.snapshot
             .borrow()
             .excerpts
             .iter()
-            .map(|entry| entry.buffer_id)
+            .map(|entry| entry.buffer.remote_id())
             .collect()
     }
 
-    pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
-        let snapshot = self.snapshot.borrow();
-        let mut ids = Vec::with_capacity(snapshot.excerpts.summary().count);
-        ids.extend(snapshot.excerpts.iter().map(|entry| entry.id));
-        ids
-    }
-
-    pub fn excerpt_containing(
-        &self,
-        position: impl ToOffset,
-        cx: &App,
-    ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
+    pub fn excerpt_containing(&self, position: impl ToOffset, cx: &App) -> Option<ExcerptInfo> {
         let snapshot = self.read(cx);
         let offset = position.to_offset(&snapshot);
 
         let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&offset);
-        cursor
-            .excerpt()
-            .or_else(|| snapshot.excerpts.last())
-            .map(|excerpt| {
-                (
-                    excerpt.id,
-                    self.buffers.get(&excerpt.buffer_id).unwrap().buffer.clone(),
-                    excerpt.range.context.clone(),
-                )
-            })
+        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(),
+        })
     }
 
     pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option<Entity<Buffer>> {
-        if let Some(buffer_id) = anchor.text_anchor.buffer_id {
-            self.buffer(buffer_id)
-        } else {
-            let (_, buffer, _) = self.excerpt_containing(anchor, cx)?;
-            Some(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,
+        };
+        self.buffer(buffer_id)
     }
 
     // If point is at the end of the buffer, the last excerpt is returned
@@ -2106,15 +1868,10 @@ impl MultiBuffer {
         &self,
         point: T,
         cx: &App,
-    ) -> Option<(Entity<Buffer>, Point, ExcerptId)> {
+    ) -> Option<(Entity<Buffer>, Point)> {
         let snapshot = self.read(cx);
-        let (buffer, point, is_main_buffer) =
-            snapshot.point_to_buffer_point(point.to_point(&snapshot))?;
-        Some((
-            self.buffers.get(&buffer.remote_id())?.buffer.clone(),
-            point,
-            is_main_buffer,
-        ))
+        let (buffer, point) = snapshot.point_to_buffer_point(point.to_point(&snapshot))?;
+        Some((self.buffers.get(&buffer.remote_id())?.buffer.clone(), point))
     }
 
     pub fn buffer_point_to_anchor(
@@ -2126,25 +1883,22 @@ impl MultiBuffer {
     ) -> Option<Anchor> {
         let mut found = None;
         let snapshot = buffer.read(cx).snapshot();
-        for (excerpt_id, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
+        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), excerpt_id));
+                found = Some(snapshot.clip_point(point, Bias::Left));
                 break;
             }
             if point < start {
-                found = Some((start, excerpt_id));
+                found = Some(start);
             }
             if point > end {
-                found = Some((end, excerpt_id));
+                found = Some(end);
             }
         }
 
-        found.map(|(point, excerpt_id)| {
-            let text_anchor = snapshot.anchor_after(point);
-            Anchor::in_buffer(excerpt_id, text_anchor)
-        })
+        Some(Anchor::text(snapshot.anchor_after(found?)))
     }
 
     pub fn buffer_anchor_to_anchor(

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -744,7 +744,7 @@ fn test_excerpt_events(cx: &mut App) {
                     predecessor,
                     excerpts,
                 } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
-                Event::ExcerptsRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
+                Event::BuffersRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
                 Event::Edited { .. } => {
                     *follower_edit_event_count.write() += 1;
                 }
@@ -3794,11 +3794,11 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
         ),
     );
 
-    let anchor_1 = Anchor::in_buffer(ids[0], text::Anchor::MIN);
+    let anchor_1 = Anchor::text(ids[0], text::Anchor::MIN);
     let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
     assert_eq!(point_1, Point::new(0, 0));
 
-    let anchor_2 = Anchor::in_buffer(ids[1], text::Anchor::MIN);
+    let anchor_2 = Anchor::text(ids[1], text::Anchor::MIN);
     let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
     assert_eq!(point_2, Point::new(3, 0));
 }
@@ -5257,8 +5257,8 @@ fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext)
         let e_b2 = snapshot.excerpt(e_b2_id).expect("E_B2 should exist");
         let e_b3 = snapshot.excerpt(e_b3_id).expect("E_B3 should exist");
 
-        let anchor_b2 = Anchor::in_buffer(e_b2_id, e_b2.range.context.start);
-        let anchor_b3 = Anchor::in_buffer(e_b3_id, e_b3.range.context.start);
+        let anchor_b2 = Anchor::text(e_b2_id, e_b2.range.context.start);
+        let anchor_b3 = Anchor::text(e_b3_id, e_b3.range.context.start);
         (anchor_b2, anchor_b3)
     });
 

crates/multi_buffer/src/path_key.rs 🔗

@@ -27,6 +27,20 @@ pub struct PathKey {
 }
 
 impl PathKey {
+    pub fn min() -> Self {
+        Self {
+            sort_prefix: None,
+            path: RelPath::empty().into_arc(),
+        }
+    }
+
+    pub fn max() -> Self {
+        Self {
+            sort_prefix: Some(u64::MAX),
+            path: RelPath::empty().into_arc(),
+        }
+    }
+
     pub fn sorted(sort_prefix: u64) -> Self {
         Self {
             sort_prefix: Some(sort_prefix),
@@ -89,7 +103,7 @@ impl MultiBuffer {
         let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
         let snapshot = self.read(cx);
         let excerpt = snapshot.excerpt(*excerpt_id)?;
-        Some(Anchor::in_buffer(excerpt.id, excerpt.range.context.start))
+        Some(Anchor::text(excerpt.id, excerpt.range.context.start))
     }
 
     pub fn set_excerpts_for_buffer(

crates/vim/src/motion.rs 🔗

@@ -2359,10 +2359,10 @@ fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) -
             ..language::ToOffset::to_offset(&range.context.end, buffer);
         if offset >= excerpt_range.start && offset <= excerpt_range.end {
             let text_anchor = buffer.anchor_after(offset);
-            let anchor = Anchor::in_buffer(excerpt, text_anchor);
+            let anchor = Anchor::text(excerpt, text_anchor);
             return anchor.to_display_point(map);
         } else if offset <= excerpt_range.start {
-            let anchor = Anchor::in_buffer(excerpt, range.context.start);
+            let anchor = Anchor::text(excerpt, range.context.start);
             return anchor.to_display_point(map);
         }
     }

crates/vim/src/normal.rs 🔗

@@ -924,7 +924,7 @@ impl Vim {
         Vim::take_forced_motion(cx);
         self.update_editor(cx, |vim, editor, cx| {
             let selection = editor.selections.newest_anchor();
-            let Some((buffer, point, _)) = editor
+            let Some((buffer, point)) = editor
                 .buffer()
                 .read(cx)
                 .point_to_buffer_point(selection.head(), cx)

crates/vim/src/state.rs 🔗

@@ -617,7 +617,7 @@ impl MarksState {
                 let text_anchors = anchors.get(name)?;
                 let anchors = text_anchors
                     .iter()
-                    .map(|anchor| Anchor::in_buffer(excerpt_id, *anchor))
+                    .map(|anchor| Anchor::text(excerpt_id, *anchor))
                     .collect();
                 return Some(Mark::Local(anchors));
             }