From 8588059f9bd580592efc676c605cb8e9faf32869 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Jan 2024 13:34:04 +0000 Subject: [PATCH] Persist the latest read item in the Tui. 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 | 114 ++++++++++++++++++++++++++++++++++------------------ src/tui.rs | 20 ++++++++- 2 files changed, 92 insertions(+), 42 deletions(-) diff --git a/src/file.rs b/src/file.rs index 07614c4..b49c417 100644 --- a/src/file.rs +++ b/src/file.rs @@ -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; fn feed_id(&self) -> Option<&FeedId> { None } - - fn save_file_position(&self, pos: FilePosition, - file_positions: &mut HashMap) { - if let Some(id) = self.feed_id() { - file_positions.insert(id.clone(), pos); - } - } } struct StatusFeedType { @@ -278,6 +272,13 @@ impl FileContents { } } + 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 { // 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 { impl File { fn new(client: &mut Client, source: Source, desc: ColouredString, - file_desc: Type, initial_pos: Option) -> + file_desc: Type, saved_pos: Option<&SavedFilePos>, + show_new: bool) -> Result { source.init(client)?; @@ -347,10 +349,33 @@ impl File { 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 File { file_desc, search_direction: None, last_search: None, - latest_read_index: None, + latest_read_index, }; Ok(ff) } @@ -1284,9 +1309,18 @@ impl } fn save_file_position( - &self, file_positions: &mut HashMap) + &self, file_positions: &mut HashMap) { - 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 } } -pub fn home_timeline(file_positions: &HashMap, +pub fn home_timeline(file_positions: &HashMap, client: &mut Client) -> Result, 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 ", - "HHHHHHHHHHHHHHHHHKH"), desc, pos)?; + "HHHHHHHHHHHHHHHHHKH"), desc, pos, false)?; Ok(Box::new(file)) } -pub fn local_timeline(file_positions: &HashMap, +pub fn local_timeline(file_positions: &HashMap, client: &mut Client) -> Result, 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 ", - "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), desc, pos)?; + "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), desc, pos, false)?; Ok(Box::new(file)) } -pub fn public_timeline(file_positions: &HashMap, +pub fn public_timeline(file_positions: &HashMap, client: &mut Client) -> Result, 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

", - "HHHHHHHHHHHHHHHHHHHKH"), desc, pos)?; + "HHHHHHHHHHHHHHHHHHHKH"), desc, pos, false)?; Ok(Box::new(file)) } -pub fn mentions(file_positions: &HashMap, +pub fn mentions(file_positions: &HashMap, client: &mut Client) -> Result, 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, +pub fn ego_log(file_positions: &HashMap, client: &mut Client) -> Result, 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, client: &mut Client, + file_positions: &HashMap, client: &mut Client, user: &str, boosts: Boosts, replies: Replies) -> Result, 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

", - "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)) } diff --git a/src/tui.rs b/src/tui.rs index f8fe360..bdac95a 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -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, + + // The id of the latest item we've actually read, which can + // persist between runs in the LDB. + pub latest_read_id: Option, +} + +impl From 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, _client: &mut Client) {} fn save_file_position( - &self, _file_positions: &mut HashMap) {} + &self, _file_positions: &mut HashMap) {} fn got_search_expression(&mut self, _dir: SearchDirection, _regex: String) -> LogicalAction { @@ -451,7 +467,7 @@ struct TuiLogicalState { activity_state: Box, overlay_activity_state: Option>, last_area: Option, - file_positions: HashMap, + file_positions: HashMap, } impl TuiLogicalState { -- 2.30.2