Merge pull request #814 from zed-industries/nav-history-scroll-position

Max Brunsfeld created

Record scroll position in nav history

Change summary

crates/editor/src/editor.rs | 35 ++++++++++++++++++++++++++++++-----
crates/editor/src/items.rs  | 15 ++++++++++++---
2 files changed, 42 insertions(+), 8 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -848,8 +848,13 @@ struct ClipboardSelection {
 }
 
 pub struct NavigationData {
-    anchor: Anchor,
-    offset: usize,
+    // Matching offsets for anchor and scroll_top_anchor allows us to recreate the anchor if the buffer
+    // has since been closed
+    cursor_anchor: Anchor,
+    cursor_offset: usize,
+    scroll_position: Vector2F,
+    scroll_top_anchor: Anchor,
+    scroll_top_offset: usize,
 }
 
 pub struct EditorCreated(pub ViewHandle<Editor>);
@@ -3896,6 +3901,7 @@ impl Editor {
             let buffer = self.buffer.read(cx).read(cx);
             let offset = position.to_offset(&buffer);
             let point = position.to_point(&buffer);
+            let scroll_top_offset = self.scroll_top_anchor.to_offset(&buffer);
             drop(buffer);
 
             if let Some(new_position) = new_position {
@@ -3906,8 +3912,11 @@ impl Editor {
             }
 
             nav_history.push(Some(NavigationData {
-                anchor: position,
-                offset,
+                cursor_anchor: position,
+                cursor_offset: offset,
+                scroll_position: self.scroll_position,
+                scroll_top_anchor: self.scroll_top_anchor.clone(),
+                scroll_top_offset,
             }));
         }
     }
@@ -6705,7 +6714,7 @@ mod tests {
         cx.set_global(Settings::test(cx));
         use workspace::Item;
         let nav_history = Rc::new(RefCell::new(workspace::NavHistory::default()));
-        let buffer = MultiBuffer::build_simple(&sample_text(30, 5, 'a'), cx);
+        let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
 
         cx.add_window(Default::default(), |cx| {
             let mut editor = build_editor(buffer.clone(), cx);
@@ -6756,6 +6765,22 @@ mod tests {
             );
             assert!(nav_history.borrow_mut().pop_backward().is_none());
 
+            // Set scroll position to check later
+            editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
+            let original_scroll_position = editor.scroll_position;
+            let original_scroll_top_anchor = editor.scroll_top_anchor.clone();
+
+            // Jump to the end of the document and adjust scroll
+            editor.move_to_end(&MoveToEnd, cx);
+            editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
+            assert_ne!(editor.scroll_position, original_scroll_position);
+            assert_ne!(editor.scroll_top_anchor, original_scroll_top_anchor);
+
+            let nav_entry = nav_history.borrow_mut().pop_backward().unwrap();
+            editor.navigate(nav_entry.data.unwrap(), cx);
+            assert_eq!(editor.scroll_position, original_scroll_position);
+            assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor);
+
             editor
         });
     }

crates/editor/src/items.rs 🔗

@@ -247,18 +247,27 @@ impl Item for Editor {
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
         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)
+            let offset = if buffer.can_resolve(&data.cursor_anchor) {
+                data.cursor_anchor.to_offset(&buffer)
             } else {
-                buffer.clip_offset(data.offset, Bias::Left)
+                buffer.clip_offset(data.cursor_offset, Bias::Left)
             };
             let newest_selection = self.newest_selection_with_snapshot::<usize>(&buffer);
+
+            let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) {
+                data.scroll_top_anchor.clone()
+            } else {
+                buffer.anchor_at(data.scroll_top_offset, Bias::Left)
+            };
+
             drop(buffer);
 
             if newest_selection.head() == offset {
                 false
             } else {
                 let nav_history = self.nav_history.take();
+                self.scroll_position = data.scroll_position;
+                self.scroll_top_anchor = scroll_top_anchor;
                 self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx);
                 self.nav_history = nav_history;
                 true