Fix relative line numbers when newest cursor offscreen

Conrad Irwin created

Change summary

crates/editor/src/element.rs | 108 ++++++++++++++++++++++++-------------
1 file changed, 69 insertions(+), 39 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -1441,34 +1441,48 @@ impl EditorElement {
 
     fn calculate_relative_line_numbers(
         &self,
+        snapshot: &EditorSnapshot,
         rows: &Range<u32>,
-        buffer_rows: &Vec<Option<u32>>,
         relative_to: Option<u32>,
-    ) -> HashMap<usize, u32> {
-        let mut relative_rows: HashMap<usize, u32> = Default::default();
-        if let Some(relative_to) = relative_to {
-            let head_idx = (relative_to - rows.start) as usize;
-            let mut delta = 1;
-            let mut i = head_idx + 1;
-            while i < buffer_rows.len() {
-                if buffer_rows[i].is_some() {
-                    relative_rows.insert(i, delta);
-                    delta += 1;
+    ) -> HashMap<u32, u32> {
+        let mut relative_rows: HashMap<u32, u32> = Default::default();
+        let Some(relative_to) = relative_to else {
+            return relative_rows;
+        };
+
+        let start = rows.start.min(relative_to);
+        let end = rows.end.max(relative_to);
+
+        let buffer_rows = snapshot
+            .buffer_rows(start)
+            .take(1 + (end - start) as usize)
+            .collect::<Vec<_>>();
+
+        let head_idx = relative_to - start;
+        let mut delta = 1;
+        let mut i = head_idx + 1;
+        while i < buffer_rows.len() as u32 {
+            if buffer_rows[i as usize].is_some() {
+                if rows.contains(&(i + start)) {
+                    relative_rows.insert(i + start, delta);
                 }
-                i += 1;
-            }
-            delta = 1;
-            i = head_idx;
-            while i > 0 && buffer_rows[i].is_none() {
-                i -= 1;
+                delta += 1;
             }
+            i += 1;
+        }
+        delta = 1;
+        i = head_idx.min(buffer_rows.len() as u32 - 1);
+        while i > 0 && buffer_rows[i as usize].is_none() {
+            i -= 1;
+        }
 
-            while i > 0 {
-                i -= 1;
-                if buffer_rows[i].is_some() {
-                    relative_rows.insert(i, delta);
-                    delta += 1;
+        while i > 0 {
+            i -= 1;
+            if buffer_rows[i as usize].is_some() {
+                if rows.contains(&(i + start)) {
+                    relative_rows.insert(i + start, delta);
                 }
+                delta += 1;
             }
         }
 
@@ -1499,25 +1513,26 @@ impl EditorElement {
             None
         };
 
-        let buffer_rows = snapshot
+        let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
+
+        for (ix, row) in snapshot
             .buffer_rows(rows.start)
             .take((rows.end - rows.start) as usize)
-            .collect::<Vec<_>>();
-
-        let relative_rows = self.calculate_relative_line_numbers(&rows, &buffer_rows, relative_to);
-
-        for (ix, row) in buffer_rows.iter().enumerate() {
+            .enumerate()
+        {
             let display_row = rows.start + ix as u32;
             let (active, color) = if active_rows.contains_key(&display_row) {
                 (true, style.line_number_active)
             } else {
                 (false, style.line_number)
             };
-            if let Some(buffer_row) = *row {
+            if let Some(buffer_row) = row {
                 if include_line_numbers {
                     line_number.clear();
                     let default_number = buffer_row + 1;
-                    let number = relative_rows.get(&ix).unwrap_or(&default_number);
+                    let number = relative_rows
+                        .get(&(ix as u32 + rows.start))
+                        .unwrap_or(&default_number);
                     write!(&mut line_number, "{}", number).unwrap();
                     line_number_layouts.push(Some(cx.text_layout_cache().layout_str(
                         &line_number,
@@ -2345,7 +2360,7 @@ impl Element<Editor> for EditorElement {
         let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
             start_row..end_row,
             &active_rows,
-            newest_selection_head,
+            newest_selection_head.or_else(|| Some(editor.selections.newest_display(cx).head())),
             is_singleton,
             &snapshot,
             cx,
@@ -3122,14 +3137,7 @@ mod tests {
 
         let relative_rows = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
-
-            let rows = 0..6;
-            let buffer_rows = snapshot
-                .buffer_rows(rows.start)
-                .take((rows.end - rows.start) as usize)
-                .collect::<Vec<_>>();
-
-            element.calculate_relative_line_numbers(&rows, &buffer_rows, Some(3))
+            element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
         });
         assert_eq!(relative_rows[&0], 3);
         assert_eq!(relative_rows[&1], 2);
@@ -3137,6 +3145,28 @@ mod tests {
         // current line has no relative number
         assert_eq!(relative_rows[&4], 1);
         assert_eq!(relative_rows[&5], 2);
+
+        // works if cursor is before screen
+        let relative_rows = editor.update(cx, |editor, cx| {
+            let snapshot = editor.snapshot(cx);
+
+            element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
+        });
+        assert_eq!(relative_rows.len(), 3);
+        assert_eq!(relative_rows[&3], 2);
+        assert_eq!(relative_rows[&4], 3);
+        assert_eq!(relative_rows[&5], 4);
+
+        // works if cursor is after screen
+        let relative_rows = editor.update(cx, |editor, cx| {
+            let snapshot = editor.snapshot(cx);
+
+            element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
+        });
+        assert_eq!(relative_rows.len(), 3);
+        assert_eq!(relative_rows[&0], 5);
+        assert_eq!(relative_rows[&1], 4);
+        assert_eq!(relative_rows[&2], 3);
     }
 
     #[gpui::test]