Use offset to restore navigation position if anchor can't be resolved

Antonio Scandurra created

Change summary

crates/editor/src/editor.rs       | 12 ++++++++++--
crates/editor/src/items.rs        | 15 +++++++++++----
crates/editor/src/multi_buffer.rs | 12 ++++++++++++
crates/text/src/text.rs           | 12 ++++++------
4 files changed, 39 insertions(+), 12 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -426,6 +426,11 @@ struct ClipboardSelection {
     is_entire_line: bool,
 }
 
+pub struct NavigationData {
+    anchor: Anchor,
+    offset: usize,
+}
+
 impl Editor {
     pub fn single_line(build_settings: BuildSettings, cx: &mut ViewContext<Self>) -> Self {
         let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
@@ -2438,9 +2443,12 @@ impl Editor {
 
     fn push_to_navigation_history(&self, cx: &mut ViewContext<Self>) {
         if let Some(navigation) = &self.navigation {
+            let buffer = self.buffer.read(cx).read(cx);
             if let Some(last_selection) = self.selections.iter().max_by_key(|s| s.id) {
-                let cursor = last_selection.head();
-                navigation.push(Some(cursor), cx);
+                let anchor = last_selection.head();
+                let offset = anchor.to_offset(&buffer);
+                drop(buffer);
+                navigation.push(Some(NavigationData { anchor, offset }), cx);
             }
         }
     }

crates/editor/src/items.rs 🔗

@@ -1,10 +1,10 @@
-use crate::{Anchor, Autoscroll, Editor, Event, MultiBuffer, ToOffset, ToPoint as _};
+use crate::{Autoscroll, Editor, Event, MultiBuffer, NavigationData, ToOffset, ToPoint as _};
 use anyhow::Result;
 use gpui::{
     elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext,
     Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle,
 };
-use language::{Buffer, Diagnostic, File as _};
+use language::{Bias, Buffer, Diagnostic, File as _};
 use postage::watch;
 use project::{File, ProjectPath, Worktree};
 use std::fmt::Write;
@@ -106,8 +106,15 @@ impl ItemView for Editor {
     }
 
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) {
-        if let Some(anchor) = data.downcast_ref::<Anchor>() {
-            let offset = anchor.to_offset(&self.buffer.read(cx).read(cx));
+        if let Some(data) = data.downcast_ref::<NavigationData>() {
+            let buffer = self.buffer.read(cx).read(cx);
+            let offset = if buffer.can_resolve(&data.anchor) {
+                data.anchor.to_offset(&buffer)
+            } else {
+                buffer.clip_offset(data.offset, Bias::Left)
+            };
+
+            drop(buffer);
             self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx);
         }
     }

crates/editor/src/multi_buffer.rs 🔗

@@ -1545,6 +1545,18 @@ impl MultiBufferSnapshot {
         panic!("excerpt not found");
     }
 
+    pub fn can_resolve(&self, anchor: &Anchor) -> bool {
+        if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() {
+            true
+        } else if let Some((buffer_id, buffer_snapshot)) =
+            self.buffer_snapshot_for_excerpt(&anchor.excerpt_id)
+        {
+            anchor.buffer_id == buffer_id && buffer_snapshot.can_resolve(&anchor.text_anchor)
+        } else {
+            false
+        }
+    }
+
     pub fn range_contains_excerpt_boundary<T: ToOffset>(&self, range: Range<T>) -> bool {
         let start = range.start.to_offset(self);
         let end = range.end.to_offset(self);

crates/text/src/text.rs 🔗

@@ -1131,12 +1131,6 @@ impl Buffer {
         }
     }
 
-    pub fn can_resolve(&self, anchor: &Anchor) -> bool {
-        *anchor == Anchor::min()
-            || *anchor == Anchor::max()
-            || self.version.observed(anchor.timestamp)
-    }
-
     pub fn peek_undo_stack(&self) -> Option<&Transaction> {
         self.history.undo_stack.last()
     }
@@ -1648,6 +1642,12 @@ impl BufferSnapshot {
         }
     }
 
+    pub fn can_resolve(&self, anchor: &Anchor) -> bool {
+        *anchor == Anchor::min()
+            || *anchor == Anchor::max()
+            || self.version.observed(anchor.timestamp)
+    }
+
     pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
         self.visible_text.clip_offset(offset, bias)
     }