chiark / gitweb /
Rework FilePosition to cache the last known width.
authorSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 11:38:40 +0000 (11:38 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 12:14:44 +0000 (12:14 +0000)
This way, it doesn't have to eagerly throw away its 'fine' position:
it can keep it until it's next called on, and then reuse it if the
new render width happens to match.

I think it's also simpler than the previous approach!

src/file.rs

index 7c13dc2f984ca979d43eb179aab6c2e5a04a7df0..28413784f73f7a1f9e4d4154a540165dff81a0c7 100644 (file)
@@ -12,17 +12,28 @@ use super::tui::{
 };
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
-enum FilePosition {
-    Coarse(isize),       // bottom of this item is at bottom of screen
-    Fine(isize, usize),  // line #n of this item is just off bottom of screen
+struct FilePosition {
+    item: isize,                // The selected item in the file
+    line: usize,                // The line number within that item
+
+    // 'line' only makes sense for a particular render width, because
+    // when items are rewrapped to a different width, their line
+    // numbers change. So we also store the width for which 'line'
+    // makes sense. Whenever we render at a different width, we must
+    // reset 'line' to the bottom of the current item.
+    //
+    // Setting this to None is a special case. It means that if
+    // line==0 then we focus the top of the item, otherwise we focus
+    // the bottom
+    width: Option<usize>,
 }
 
 impl FilePosition {
-    fn item(&self) -> isize {
-        match self {
-            FilePosition::Coarse(item) => *item,
-            FilePosition::Fine(item, _) => *item,
-        }
+    fn item_top(item: isize) -> Self {
+        FilePosition { item, line: 0, width: None }
+    }
+    fn item_bottom(item: isize) -> Self {
+        FilePosition { item, line: 1, width: None }
     }
 }
 
@@ -268,7 +279,8 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
         contents.update_items(client);
 
         // FIXME: once we have an LDB, that's where initial pos comes from
-        let initial_pos = FilePosition::Coarse(contents.last_index() as isize);
+        let initial_pos = FilePosition::item_bottom(
+            contents.last_index() as isize);
 
         let ff = File {
             contents,
@@ -318,13 +330,13 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
         let (w, h) = self.last_size.expect(
             "ensure_enough_rendered before resize");
 
-        let (item, line) = self.refine_pos(w);
+        self.update_pos_for_size(w, h);
 
-        let mut item = item;
+        let mut item = self.pos.item;
         let mut lines_rendered = 0;
         // We count most items we render for their full height, but
         // not the one we can only see the top segment of
-        let mut line_count_override = Some(line);
+        let mut line_count_override = Some(self.pos.line);
 
         loop {
             let item_height = self.ensure_item_rendered(item, w).len();
@@ -369,27 +381,17 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
         }
     }
 
-    fn refine_pos(&mut self, w: usize) -> (isize, usize) {
-        let (newpos, item, line) = match self.pos {
-            pos @ FilePosition::Fine(item, line) => (pos, item, line),
-            FilePosition::Coarse(item) => {
-                let rendered = self.ensure_item_rendered(item, w);
-                let line = rendered.len();
-                (FilePosition::Fine(item, line), item, line)
-            }
+    fn update_pos_for_size(&mut self, w: usize, h: usize) {
+        if self.pos.width == None && self.pos.line == 0 {
+            // Special case: look at the _top_ of the item
+            self.pos.width = Some(w);
+            self.move_down(h.saturating_sub(1));
+        } else if self.pos.width != Some(w) {
+            let rendered = self.ensure_item_rendered(self.pos.item, w);
+            self.pos.line = rendered.len();
+            self.pos.width = Some(w);
         };
-
-        self.pos = newpos;
-        (item, line)
-    }
-
-    fn coarsen_pos(&mut self) {
-        let newpos = match self.pos {
-            pos @ FilePosition::Coarse(_) => pos,
-            FilePosition::Fine(item, _line) => FilePosition::Coarse(item),
-        };
-
-        self.pos = newpos;
+        dbg!(self.pos);
     }
 
     fn fix_overshoot_at_top(&mut self) {
@@ -407,59 +409,48 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
     fn at_top(&mut self) -> bool { self.ensure_enough_rendered().is_some() }
 
     fn move_up(&mut self, distance: usize) {
-        let (w, _h) = self.last_size.expect("move_up before resize");
-        let (item, line) = match self.pos {
-            FilePosition::Fine(item, line) => (item, line),
-            _ => panic!("coarse position reached move_up()"),
-        };
+        let (w, h) = self.last_size.expect("move_up before resize");
+        self.update_pos_for_size(w, h);
 
-        let mut item = item;
-        let mut line = line;
         let mut remaining = distance;
         while remaining > 0 {
-            let this = min(line, remaining);
+            let this = min(self.pos.line, remaining);
             remaining -= this;
-            line -= this;
-            if line == 0 {
-                if item <= self.contents.first_index() {
+            self.pos.line -= this;
+            if self.pos.line == 0 {
+                if self.pos.item <= self.contents.first_index() {
                     break;
                 }
-                item -= 1;
-                line = self.ensure_item_rendered(item, w).len();
+                self.pos.item -= 1;
+                self.pos.line = self.ensure_item_rendered(self.pos.item, w)
+                    .len();
             }
         }
-        self.pos = FilePosition::Fine(item, line);
         self.fix_overshoot_at_top();
         self.after_setting_pos();
     }
 
     fn move_down_inner(&mut self, distance: usize) {
-        let (w, _h) = self.last_size.expect("move_down before resize");
-        let (item, line) = match self.pos {
-            FilePosition::Fine(item, line) => (item, line),
-            _ => panic!("coarse position reached move_down()"),
-        };
+        let (w, h) = self.last_size.expect("move_down before resize");
+        self.update_pos_for_size(w, h);
 
-        let mut item = item;
-        let mut line = line;
         let mut remaining = distance;
         while remaining > 0 {
-            let mut len = self.ensure_item_rendered(item, w).len();
-            if line == len {
-                if item >= self.contents.last_index() {
+            let mut len = self.ensure_item_rendered(self.pos.item, w).len();
+            if self.pos.line == len {
+                if self.pos.item >= self.contents.last_index() {
                     break;
                 }
-                item += 1;
-                line = 0;
-                len = self.ensure_item_rendered(item, w).len();
+                self.pos.item += 1;
+                self.pos.line = 0;
+                len = self.ensure_item_rendered(self.pos.item, w).len();
             }
 
-            let this = min(remaining, len - line);
+            let this = min(remaining, len - self.pos.line);
             remaining -= this;
-            line += this;
+            self.pos.line += this;
         }
 
-        self.pos = FilePosition::Fine(item, line);
         self.ensure_enough_rendered();
     }
 
@@ -469,16 +460,13 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
     }
 
     fn goto_top(&mut self) {
-        self.pos = FilePosition::Fine(self.contents.first_index(), 0);
+        self.pos = FilePosition::item_top(self.contents.first_index());
         self.fix_overshoot_at_top();
         self.after_setting_pos();
     }
 
     fn goto_bottom(&mut self) {
-        let (w, _h) = self.last_size.expect("goto_bottom before resize");
-        let item = self.contents.last_index();
-        let line = self.ensure_item_rendered(item, w).len();
-        self.pos = FilePosition::Fine(item, line);
+        self.pos = FilePosition::item_bottom(self.contents.last_index());
         self.ensure_enough_rendered();
         self.after_setting_pos();
     }
@@ -494,14 +482,9 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
                 if !any_new {
                     // Can't extend this any further into the past
                     self.contents.extender = None;
-                    if self.pos.item() < self.contents.origin {
-                        self.coarsen_pos();
-                        if self.pos.item() <
-                            self.contents.first_index()
-                        {
-                            self.pos = FilePosition::Coarse(
-                                self.contents.first_index());
-                        }
+                    if self.pos.item < self.contents.first_index() {
+                        self.pos = FilePosition::item_top(
+                            self.contents.first_index());
                     }
                 }
 
@@ -551,7 +534,7 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
                        purpose: SelectionPurpose, client: &mut Client)
                        -> LogicalAction
     {
-        let item = self.pos.item();
+        let item = self.pos.item;
         let selection =
             self.last_selectable_above(htype, item).or_else(
                 || self.first_selectable_below(htype, item + 1));
@@ -742,8 +725,9 @@ impl<Type: FileType, Source: FileDataSource>
     fn resize(&mut self, w: usize, h: usize) {
         if self.last_size != Some((w, h)) {
             self.last_size = Some((w, h));
-            self.coarsen_pos();
             self.rendered.clear();
+            self.update_pos_for_size(w, h);
+            self.fix_overshoot_at_top();
         }
         self.ensure_enough_rendered();
         self.after_setting_pos();
@@ -753,10 +737,9 @@ impl<Type: FileType, Source: FileDataSource>
             -> (Vec<ColouredString>, CursorPosition) {
         assert_eq!(self.last_size, Some((w, h)),
                    "last resize() inconsistent with draw()");
-        let (start_item, start_line) = match self.pos {
-            FilePosition::Fine(item, line) => (item, line),
-            _ => panic!("coarse position reached draw()"),
-        };
+        assert_eq!(self.pos.width, Some(w),
+                   "file position inconsistent with draw()");
+        let (start_item, start_line) = (self.pos.item, self.pos.line);
 
         let mut item = start_item;
         let mut lines = Vec::new();