Make rename highlights work across multibuffer excerpts

Keith Simmons , Antonio Scandurra , and Nathan Sobo created

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/diagnostics.rs    |  3 +
crates/editor/src/editor.rs              | 47 +++++++++++++++++--------
crates/editor/src/multi_buffer.rs        | 36 ++++++++++++++++---
crates/editor/src/multi_buffer/anchor.rs |  4 +-
crates/editor/src/repro.rs               |  9 ++++
crates/language/src/diagnostic_set.rs    |  8 ++--
crates/language/src/tests.rs             |  2 
crates/text/src/anchor.rs                | 20 +++++++++-
crates/text/src/text.rs                  | 26 +++++++-------
9 files changed, 110 insertions(+), 45 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -278,7 +278,8 @@ impl ProjectDiagnosticsEditor {
                             prev_excerpt_id = excerpt_id.clone();
                             first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
                             group_state.excerpts.push(excerpt_id.clone());
-                            let header_position = (excerpt_id.clone(), language::Anchor::min());
+                            let header_position =
+                                (excerpt_id.clone(), language::Anchor::build_min());
 
                             if is_first_excerpt_for_group {
                                 is_first_excerpt_for_group = false;

crates/editor/src/editor.rs 🔗

@@ -2399,7 +2399,7 @@ impl Editor {
     ) -> Result<()> {
         let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx));
 
-        // If the code action's edits are all contained within this editor, then
+        // If the project transaction's edits are all contained within this editor, then
         // avoid opening a new editor to display them.
         let mut entries = transaction.0.iter();
         if let Some((buffer, transaction)) = entries.next() {
@@ -2521,7 +2521,6 @@ impl Editor {
                     }
 
                     let buffer_id = cursor_position.buffer_id;
-                    let excerpt_id = cursor_position.excerpt_id.clone();
                     let style = this.style(cx);
                     let read_background = style.document_highlight_read_background;
                     let write_background = style.document_highlight_write_background;
@@ -2533,22 +2532,39 @@ impl Editor {
                         return;
                     }
 
+                    let cursor_buffer_snapshot = cursor_buffer.read(cx);
                     let mut write_ranges = Vec::new();
                     let mut read_ranges = Vec::new();
                     for highlight in highlights {
-                        let range = Anchor {
-                            buffer_id,
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: highlight.range.start,
-                        }..Anchor {
-                            buffer_id,
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: highlight.range.end,
-                        };
-                        if highlight.kind == lsp::DocumentHighlightKind::WRITE {
-                            write_ranges.push(range);
-                        } else {
-                            read_ranges.push(range);
+                        for (excerpt_id, excerpt_range) in
+                            buffer.excerpts_for_buffer(&cursor_buffer, cx)
+                        {
+                            let start = highlight
+                                .range
+                                .start
+                                .max(&excerpt_range.start, cursor_buffer_snapshot);
+                            let end = highlight
+                                .range
+                                .end
+                                .min(&excerpt_range.end, cursor_buffer_snapshot);
+                            if start.cmp(&end, cursor_buffer_snapshot).unwrap().is_ge() {
+                                continue;
+                            }
+
+                            let range = Anchor {
+                                buffer_id,
+                                excerpt_id: excerpt_id.clone(),
+                                text_anchor: start,
+                            }..Anchor {
+                                buffer_id,
+                                excerpt_id,
+                                text_anchor: end,
+                            };
+                            if highlight.kind == lsp::DocumentHighlightKind::WRITE {
+                                write_ranges.push(range);
+                            } else {
+                                read_ranges.push(range);
+                            }
                         }
                     }
 
@@ -4413,7 +4429,6 @@ impl Editor {
                         .iter()
                         .map(|range| range.to_point(&snapshot))
                         .collect::<Vec<_>>();
-                    dbg!(point_ranges);
 
                     this.highlight_text::<Rename>(
                         ranges,

crates/editor/src/multi_buffer.rs 🔗

@@ -211,7 +211,11 @@ impl MultiBuffer {
     pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
         let mut this = Self::new(buffer.read(cx).replica_id());
         this.singleton = true;
-        this.push_excerpts(buffer, [text::Anchor::min()..text::Anchor::max()], cx);
+        this.push_excerpts(
+            buffer,
+            [text::Anchor::build_min()..text::Anchor::build_max()],
+            cx,
+        );
         this.snapshot.borrow_mut().singleton = true;
         this
     }
@@ -814,11 +818,30 @@ impl MultiBuffer {
         cx.notify();
     }
 
-    pub fn excerpt_ids_for_buffer(&self, buffer: &ModelHandle<Buffer>) -> Vec<ExcerptId> {
-        self.buffers
-            .borrow()
+    pub fn excerpts_for_buffer(
+        &self,
+        buffer: &ModelHandle<Buffer>,
+        cx: &AppContext,
+    ) -> Vec<(ExcerptId, Range<text::Anchor>)> {
+        let mut excerpts = Vec::new();
+        let snapshot = self.read(cx);
+        let buffers = self.buffers.borrow();
+        let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
+        for excerpt_id in buffers
             .get(&buffer.id())
-            .map_or(Vec::new(), |state| state.excerpts.clone())
+            .map(|state| &state.excerpts)
+            .into_iter()
+            .flatten()
+        {
+            cursor.seek_forward(&Some(excerpt_id), Bias::Left, &());
+            if let Some(excerpt) = cursor.item() {
+                if excerpt.id == *excerpt_id {
+                    excerpts.push((excerpt.id.clone(), excerpt.range.clone()));
+                }
+            }
+        }
+
+        excerpts
     }
 
     pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
@@ -3070,7 +3093,8 @@ mod tests {
         );
 
         let snapshot = multibuffer.update(cx, |multibuffer, cx| {
-            let buffer_2_excerpt_id = multibuffer.excerpt_ids_for_buffer(&buffer_2)[0].clone();
+            let (buffer_2_excerpt_id, _) =
+                multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
             multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx);
             multibuffer.snapshot(cx)
         });

crates/editor/src/multi_buffer/anchor.rs 🔗

@@ -19,7 +19,7 @@ impl Anchor {
         Self {
             buffer_id: None,
             excerpt_id: ExcerptId::min(),
-            text_anchor: text::Anchor::min(),
+            text_anchor: text::Anchor::build_min(),
         }
     }
 
@@ -27,7 +27,7 @@ impl Anchor {
         Self {
             buffer_id: None,
             excerpt_id: ExcerptId::max(),
-            text_anchor: text::Anchor::max(),
+            text_anchor: text::Anchor::build_max(),
         }
     }
 

crates/editor/src/repro.rs 🔗

@@ -0,0 +1,9 @@
+fn test(complicated: i32, test: i32) {
+    complicated;
+    test;
+    // 1
+    // 2
+    // 3
+    complicated;
+    test;
+}

crates/language/src/diagnostic_set.rs 🔗

@@ -187,10 +187,10 @@ impl DiagnosticEntry<Anchor> {
 impl Default for Summary {
     fn default() -> Self {
         Self {
-            start: Anchor::min(),
-            end: Anchor::max(),
-            min_start: Anchor::max(),
-            max_end: Anchor::min(),
+            start: Anchor::build_min(),
+            end: Anchor::build_max(),
+            min_start: Anchor::build_max(),
+            max_end: Anchor::build_min(),
             count: 0,
         }
     }

crates/language/src/tests.rs 🔗

@@ -789,7 +789,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
     for buffer in &buffers {
         let buffer = buffer.read(cx).snapshot();
         let actual_remote_selections = buffer
-            .remote_selections_in_range(Anchor::min()..Anchor::max())
+            .remote_selections_in_range(Anchor::build_min()..Anchor::build_max())
             .map(|(replica_id, selections)| (replica_id, selections.collect::<Vec<_>>()))
             .collect::<Vec<_>>();
         let expected_remote_selections = active_selections

crates/text/src/anchor.rs 🔗

@@ -12,7 +12,7 @@ pub struct Anchor {
 }
 
 impl Anchor {
-    pub fn min() -> Self {
+    pub fn build_min() -> Self {
         Self {
             timestamp: clock::Local::MIN,
             offset: usize::MIN,
@@ -20,7 +20,7 @@ impl Anchor {
         }
     }
 
-    pub fn max() -> Self {
+    pub fn build_max() -> Self {
         Self {
             timestamp: clock::Local::MAX,
             offset: usize::MAX,
@@ -42,6 +42,22 @@ impl Anchor {
             .then_with(|| self.bias.cmp(&other.bias)))
     }
 
+    pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+        if self.cmp(other, buffer).unwrap().is_le() {
+            self.clone()
+        } else {
+            other.clone()
+        }
+    }
+
+    pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+        if self.cmp(other, buffer).unwrap().is_ge() {
+            self.clone()
+        } else {
+            other.clone()
+        }
+    }
+
     pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor {
         if bias == Bias::Left {
             self.bias_left(buffer)

crates/text/src/text.rs 🔗

@@ -1318,8 +1318,8 @@ impl Buffer {
         let mut futures = Vec::new();
         for anchor in anchors {
             if !self.version.observed(anchor.timestamp)
-                && *anchor != Anchor::max()
-                && *anchor != Anchor::min()
+                && *anchor != Anchor::build_max()
+                && *anchor != Anchor::build_min()
             {
                 let (tx, rx) = oneshot::channel();
                 self.edit_id_resolvers
@@ -1638,9 +1638,9 @@ impl BufferSnapshot {
         let mut position = D::default();
 
         anchors.map(move |anchor| {
-            if *anchor == Anchor::min() {
+            if *anchor == Anchor::build_min() {
                 return D::default();
-            } else if *anchor == Anchor::max() {
+            } else if *anchor == Anchor::build_max() {
                 return D::from_text_summary(&self.visible_text.summary());
             }
 
@@ -1680,9 +1680,9 @@ impl BufferSnapshot {
     where
         D: TextDimension,
     {
-        if *anchor == Anchor::min() {
+        if *anchor == Anchor::build_min() {
             D::default()
-        } else if *anchor == Anchor::max() {
+        } else if *anchor == Anchor::build_max() {
             D::from_text_summary(&self.visible_text.summary())
         } else {
             let anchor_key = InsertionFragmentKey {
@@ -1718,9 +1718,9 @@ impl BufferSnapshot {
     }
 
     fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
-        if *anchor == Anchor::min() {
+        if *anchor == Anchor::build_min() {
             &locator::MIN
-        } else if *anchor == Anchor::max() {
+        } else if *anchor == Anchor::build_max() {
             &locator::MAX
         } else {
             let anchor_key = InsertionFragmentKey {
@@ -1758,9 +1758,9 @@ impl BufferSnapshot {
     pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
         let offset = position.to_offset(self);
         if bias == Bias::Left && offset == 0 {
-            Anchor::min()
+            Anchor::build_min()
         } else if bias == Bias::Right && offset == self.len() {
-            Anchor::max()
+            Anchor::build_max()
         } else {
             let mut fragment_cursor = self.fragments.cursor::<usize>();
             fragment_cursor.seek(&offset, bias, &None);
@@ -1775,8 +1775,8 @@ impl BufferSnapshot {
     }
 
     pub fn can_resolve(&self, anchor: &Anchor) -> bool {
-        *anchor == Anchor::min()
-            || *anchor == Anchor::max()
+        *anchor == Anchor::build_min()
+            || *anchor == Anchor::build_max()
             || self.version.observed(anchor.timestamp)
     }
 
@@ -1799,7 +1799,7 @@ impl BufferSnapshot {
     where
         D: TextDimension + Ord,
     {
-        self.edits_since_in_range(since, Anchor::min()..Anchor::max())
+        self.edits_since_in_range(since, Anchor::build_min()..Anchor::build_max())
     }
 
     pub fn edited_ranges_for_transaction<'a, D>(