chiark / gitweb /
Persist the latest read item in the Tui.
authorSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 13:34:04 +0000 (13:34 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 14:06:13 +0000 (14:06 +0000)
Now it's used to set the initial position when File is constructed,
but there's still no actual effect, because the _position_ from the
last File instance on that feed still takes precedence, and we never
save anything into the Tui without one of those.

src/file.rs
src/tui.rs

index 07614c4af4af06e2ab2727b6697f05de5b1827cf..b49c417f398a8855091ffc13db7ab3190bfee513 100644 (file)
@@ -12,6 +12,7 @@ use super::text::*;
 use super::tui::{
     ActivityState, CursorPosition, LogicalAction,
     OurKey, OurKey::*,
+    SavedFilePos,
 };
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -138,13 +139,6 @@ trait FileType {
         Result<Self::Item, ClientError>;
 
     fn feed_id(&self) -> Option<&FeedId> { None }
-
-    fn save_file_position(&self, pos: FilePosition,
-                          file_positions: &mut HashMap<FeedId, FilePosition>) {
-        if let Some(id) = self.feed_id() {
-            file_positions.insert(id.clone(), pos);
-        }
-    }
 }
 
 struct StatusFeedType {
@@ -278,6 +272,13 @@ impl<Type: FileType, Source: FileDataSource> FileContents<Type,Source> {
         }
     }
 
+    fn id_at_index(&self, index: isize) -> Option<&str> {
+        index.checked_sub(self.origin)
+            .and_then(|i: isize| i.try_into().ok())
+            .and_then(|u: usize| self.items.get(u))
+            .map(|item: &(String, Type::Item)| &item.0 as &str)
+    }
+
     fn index_of_id(&self, id: &str) -> Option<isize> {
         // We can't do anything efficient like binary search, because
         // our ids might not be in any sensible order. (If they're,
@@ -326,7 +327,8 @@ struct File<Type: FileType, Source: FileDataSource> {
 
 impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
     fn new(client: &mut Client, source: Source, desc: ColouredString,
-           file_desc: Type, initial_pos: Option<FilePosition>) ->
+           file_desc: Type, saved_pos: Option<&SavedFilePos>,
+           show_new: bool) ->
         Result<Self, ClientError>
     {
         source.init(client)?;
@@ -347,10 +349,33 @@ 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 mut initial_pos = initial_pos.unwrap_or_else(
-            || FilePosition::item_bottom(isize::MAX));
+        // Start with the initial position at the file top
+        let mut initial_pos = FilePosition::item_top(contents.first_index());
+
+        // If we have a 'latest read item' from the saved file
+        // position, determine it, and override the initial position
+        // with it
+        let mut latest_read_index = None;
+        if let Some(saved_pos) = saved_pos {
+            latest_read_index = saved_pos.latest_read_id.as_ref().and_then(
+                |id| contents.index_of_id(id));
 
+            if let Some(latest_read_index) = latest_read_index {
+                initial_pos = if show_new {
+                    FilePosition::item_top(latest_read_index + 1)
+                } else {
+                    FilePosition::item_bottom(latest_read_index)
+                }
+            }
+        }
+
+        // But if we have an actual FilePosition in our SavedFilePos,
+        // it's even better to use that.
+        if let Some(file_pos) = saved_pos.and_then(|sp| sp.file_pos) {
+            initial_pos = file_pos;
+        }
+
+        // Now clip initial_pos at the top and bottom of the data we have
         initial_pos = initial_pos.clip(
             contents.first_index(), contents.last_index());
 
@@ -365,7 +390,7 @@ impl<Type: FileType, Source: FileDataSource> File<Type, Source> {
             file_desc,
             search_direction: None,
             last_search: None,
-            latest_read_index: None,
+            latest_read_index,
         };
         Ok(ff)
     }
@@ -1284,9 +1309,18 @@ impl<Type: FileType, Source: FileDataSource>
     }
 
     fn save_file_position(
-        &self, file_positions: &mut HashMap<FeedId, FilePosition>)
+        &self, file_positions: &mut HashMap<FeedId, SavedFilePos>)
     {
-        self.file_desc.save_file_position(self.pos, file_positions);
+        if let Some(id) = self.file_desc.feed_id() {
+            let sfp = SavedFilePos {
+                file_pos: Some(self.pos),
+                latest_read_id: self.latest_read_index
+                    .and_then(|i| self.contents.id_at_index(i))
+                    .map(|s| s.to_owned()),
+            };
+            file_positions.insert(id.clone(), sfp);
+            todo!();
+        }
     }
 
     fn got_search_expression(&mut self, dir: SearchDirection, regex: String)
@@ -1303,88 +1337,88 @@ impl<Type: FileType, Source: FileDataSource>
     }
 }
 
-pub fn home_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+pub fn home_timeline(file_positions: &HashMap<FeedId, SavedFilePos>,
                      client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::Home;
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Home timeline   <H>",
-            "HHHHHHHHHHHHHHHHHKH"), desc, pos)?;
+            "HHHHHHHHHHHHHHHHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
-pub fn local_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+pub fn local_timeline(file_positions: &HashMap<FeedId, SavedFilePos>,
                       client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::Local;
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Local public timeline   <L>",
-            "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), desc, pos)?;
+            "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
-pub fn public_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+pub fn public_timeline(file_positions: &HashMap<FeedId, SavedFilePos>,
                        client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::Public;
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Public timeline   <P>",
-            "HHHHHHHHHHHHHHHHHHHKH"), desc, pos)?;
+            "HHHHHHHHHHHHHHHHHHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
-pub fn mentions(file_positions: &HashMap<FeedId, FilePosition>,
+pub fn mentions(file_positions: &HashMap<FeedId, SavedFilePos>,
                 client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::Mentions;
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = NotificationStatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Mentions   [ESC][R]",
-            "HHHHHHHHHHHHKKKHHKH"), desc, pos)?;
+            "HHHHHHHHHHHHKKKHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
-pub fn ego_log(file_positions: &HashMap<FeedId, FilePosition>,
+pub fn ego_log(file_positions: &HashMap<FeedId, SavedFilePos>,
                client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::Ego;
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = EgoNotificationFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Ego Log   [ESC][L][L][E]",
-            "HHHHHHHHHHHKKKHHKHHKHHKH"), desc, pos)?;
+            "HHHHHHHHHHHKKKHHKHHKHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
 pub fn user_posts(
-    file_positions: &HashMap<FeedId, FilePosition>, client: &mut Client,
+    file_positions: &HashMap<FeedId, SavedFilePos>, client: &mut Client,
     user: &str, boosts: Boosts, replies: Replies)
     -> Result<Box<dyn ActivityState>, ClientError>
 {
     let feed = FeedId::User(user.to_owned(), boosts, replies);
-    let pos = file_positions.get(&feed).cloned();
+    let pos = file_positions.get(&feed);
     let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(feed), ColouredString::general(
             "Public timeline   <P>",
-            "HHHHHHHHHHHHHHHHHHHKH"), desc, pos)?;
+            "HHHHHHHHHHHHHHHHHHHKH"), desc, pos, false)?;
     Ok(Box::new(file))
 }
 
@@ -1395,7 +1429,7 @@ pub fn list_status_favouriters(client: &mut Client, id: &str) ->
         client, FeedSource::new(FeedId::Favouriters(id.to_owned())),
         ColouredString::uniform(
             &format!("Users who favourited post {id}"), 'H'),
-        UserListFeedType{}, None)?;
+        UserListFeedType{}, None, false)?;
     Ok(Box::new(file))
 }
 
@@ -1406,7 +1440,7 @@ pub fn list_status_boosters(client: &mut Client, id: &str) ->
         client, FeedSource::new(FeedId::Boosters(id.to_owned())),
         ColouredString::uniform(
             &format!("Users who boosted post {id}"), 'H'),
-        UserListFeedType{}, None)?;
+        UserListFeedType{}, None, false)?;
     Ok(Box::new(file))
 }
 
@@ -1420,7 +1454,7 @@ pub fn list_user_followers(client: &mut Client, id: &str) ->
         client, FeedSource::new(FeedId::Followers(id.to_owned())),
         ColouredString::uniform(
             &format!("Users who follow {name}"), 'H'),
-        UserListFeedType{}, None)?;
+        UserListFeedType{}, None, false)?;
     Ok(Box::new(file))
 }
 
@@ -1434,7 +1468,7 @@ pub fn list_user_followees(client: &mut Client, id: &str) ->
         client, FeedSource::new(FeedId::Followees(id.to_owned())),
         ColouredString::uniform(
             &format!("Users who {name} follows"), 'H'),
-        UserListFeedType{}, None)?;
+        UserListFeedType{}, None, false)?;
     Ok(Box::new(file))
 }
 
@@ -1447,7 +1481,7 @@ pub fn hashtag_timeline(client: &mut Client, tag: &str) ->
     let desc = StatusFeedType::with_feed(feed);
     let file = File::new(
         client, FeedSource::new(FeedId::Hashtag(tag.to_owned())), title,
-        desc, None)?;
+        desc, None, false)?;
     Ok(Box::new(file))
 }
 
@@ -1475,7 +1509,7 @@ pub fn examine_user(client: &mut Client, account_id: &str) ->
 
     let file = File::new(
         client, StaticSource::singleton(ac.id), title, ExamineUserFileType{},
-        Some(FilePosition::item_top(isize::MIN)))?;
+        Some(&FilePosition::item_top(isize::MIN).into()), false)?;
     Ok(Box::new(file))
 }
 
@@ -1502,7 +1536,7 @@ pub fn view_single_post(client: &mut Client, status_id: &str) ->
     let file = File::new(
         client, StaticSource::singleton(st.id), title,
         DetailedStatusFileType{},
-        Some(FilePosition::item_top(isize::MIN)))?;
+        Some(&FilePosition::item_top(isize::MIN).into()), false)?;
     Ok(Box::new(file))
 }
 
@@ -1544,6 +1578,6 @@ pub fn view_thread(client: &mut Client, start_id: &str, full: bool) ->
     let file = File::new(
         client, StaticSource::vector(ids), title,
         StatusFeedType::without_feed(),
-        Some(FilePosition::item_top(index)))?;
+        Some(&FilePosition::item_top(index).into()), false)?;
     Ok(Box::new(file))
 }
index f8fe3609049046f8eeaeab221d25aa1ac3d571b0..bdac95a81e01cca4994142f291902f57e66510d2 100644 (file)
@@ -429,6 +429,22 @@ pub enum LogicalAction {
     PostReEdit(Post),
 }
 
+pub struct SavedFilePos {
+    // The current position we're _looking at_ within the file, within
+    // this run
+    pub file_pos: Option<FilePosition>,
+
+    // The id of the latest item we've actually read, which can
+    // persist between runs in the LDB.
+    pub latest_read_id: Option<String>,
+}
+
+impl From<FilePosition> for SavedFilePos {
+    fn from(file_pos: FilePosition) -> Self {
+        SavedFilePos { file_pos: Some(file_pos), latest_read_id: None }
+    }
+}
+
 pub trait ActivityState {
     fn resize(&mut self, _w: usize, _h: usize) {}
     fn draw(&self, w: usize, h: usize) ->
@@ -438,7 +454,7 @@ pub trait ActivityState {
     fn handle_feed_updates(&mut self, _feeds_updated: &HashSet<FeedId>,
                            _client: &mut Client) {}
     fn save_file_position(
-        &self, _file_positions: &mut HashMap<FeedId, FilePosition>) {}
+        &self, _file_positions: &mut HashMap<FeedId, SavedFilePos>) {}
     fn got_search_expression(&mut self, _dir: SearchDirection, _regex: String)
                              -> LogicalAction
     {
@@ -451,7 +467,7 @@ struct TuiLogicalState {
     activity_state: Box<dyn ActivityState>,
     overlay_activity_state: Option<Box<dyn ActivityState>>,
     last_area: Option<Rect>,
-    file_positions: HashMap<FeedId, FilePosition>,
+    file_positions: HashMap<FeedId, SavedFilePos>,
 }
 
 impl TuiLogicalState {