chiark / gitweb /
Save and restore file positions when changing activity.
authorSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 12:24:28 +0000 (12:24 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 13:17:45 +0000 (13:17 +0000)
Now you go back to where you last were in the file.

src/file.rs
src/tui.rs

index ea66e26de8038e079f731bb8285d2786be69e67f..dac6e1aec4cae95f51aabb8a8df90d548c996f0f 100644 (file)
@@ -12,7 +12,7 @@ use super::tui::{
 };
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
-struct FilePosition {
+pub struct FilePosition {
     item: isize,                // The selected item in the file
     line: usize,                // The line number within that item
 
@@ -117,9 +117,24 @@ trait FileType {
 
     fn get_from_client(id: &str, client: &mut Client) ->
         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 {}
+struct StatusFeedType {
+    id: Option<FeedId>,
+}
+impl StatusFeedType {
+    fn with_feed(id: FeedId) -> Self { Self { id: Some(id) } }
+    fn without_feed() -> Self { Self { id: None } }
+}
 impl FileType for StatusFeedType {
     type Item = StatusDisplay;
 
@@ -129,9 +144,16 @@ impl FileType for StatusFeedType {
         let st = client.status_by_id(&id)?;
         Ok(StatusDisplay::new(st.clone(), client))
     }
+
+    fn feed_id(&self) -> Option<&FeedId> { self.id.as_ref() }
 }
 
-struct NotificationStatusFeedType {}
+struct NotificationStatusFeedType {
+    id: FeedId,
+}
+impl NotificationStatusFeedType {
+    fn with_feed(id: FeedId) -> Self { Self { id } }
+}
 impl FileType for NotificationStatusFeedType {
     type Item = StatusDisplay;
 
@@ -143,9 +165,16 @@ impl FileType for NotificationStatusFeedType {
             "expected all notifications in this feed would have statuses");
         Ok(StatusDisplay::new(st.clone(), client))
     }
+
+    fn feed_id(&self) -> Option<&FeedId> { Some(&self.id) }
 }
 
-struct EgoNotificationFeedType {}
+struct EgoNotificationFeedType {
+    id: FeedId,
+}
+impl EgoNotificationFeedType {
+    fn with_feed(id: FeedId) -> Self { Self { id } }
+}
 impl FileType for EgoNotificationFeedType {
     type Item = NotificationLog;
 
@@ -155,6 +184,8 @@ impl FileType for EgoNotificationFeedType {
         let not = client.notification_by_id(&id)?;
         Ok(NotificationLog::from_notification(&not, client))
     }
+
+    fn feed_id(&self) -> Option<&FeedId> { Some(&self.id) }
 }
 
 struct UserListFeedType {}
@@ -1089,55 +1120,81 @@ impl<Type: FileType, Source: FileDataSource>
             self.fix_overshoot_at_top();
         }
     }
+
+    fn save_file_position(
+        &self, file_positions: &mut HashMap<FeedId, FilePosition>)
+    {
+        self.file_desc.save_file_position(self.pos, file_positions);
+    }
 }
 
-pub fn home_timeline(client: &mut Client) ->
+pub fn home_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+                     client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
+    let feed = FeedId::Home;
+    let pos = file_positions.get(&feed).cloned();
+    let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
-        client, FeedSource::new(FeedId::Home), ColouredString::general(
+        client, FeedSource::new(feed), ColouredString::general(
             "Home timeline   <H>",
-            "HHHHHHHHHHHHHHHHHKH"), StatusFeedType{}, None)?;
+            "HHHHHHHHHHHHHHHHHKH"), desc, pos)?;
     Ok(Box::new(file))
 }
 
-pub fn local_timeline(client: &mut Client) ->
+pub fn local_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+                      client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
+    let feed = FeedId::Local;
+    let pos = file_positions.get(&feed).cloned();
+    let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
-        client, FeedSource::new(FeedId::Local), ColouredString::general(
+        client, FeedSource::new(feed), ColouredString::general(
             "Local public timeline   <L>",
-            "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), StatusFeedType{}, None)?;
+            "HHHHHHHHHHHHHHHHHHHHHHHHHKH"), desc, pos)?;
     Ok(Box::new(file))
 }
 
-pub fn public_timeline(client: &mut Client) ->
+pub fn public_timeline(file_positions: &HashMap<FeedId, FilePosition>,
+                       client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
+    let feed = FeedId::Public;
+    let pos = file_positions.get(&feed).cloned();
+    let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
-        client, FeedSource::new(FeedId::Public), ColouredString::general(
+        client, FeedSource::new(feed), ColouredString::general(
             "Public timeline   <P>",
-            "HHHHHHHHHHHHHHHHHHHKH"), StatusFeedType{}, None)?;
+            "HHHHHHHHHHHHHHHHHHHKH"), desc, pos)?;
     Ok(Box::new(file))
 }
 
-pub fn mentions(client: &mut Client) ->
+pub fn mentions(file_positions: &HashMap<FeedId, FilePosition>,
+                client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
+    let feed = FeedId::Mentions;
+    let pos = file_positions.get(&feed).cloned();
+    let desc = NotificationStatusFeedType::with_feed(feed.clone());
     let file = File::new(
-        client, FeedSource::new(FeedId::Mentions), ColouredString::general(
+        client, FeedSource::new(feed), ColouredString::general(
             "Mentions   [ESC][R]",
-            "HHHHHHHHHHHHKKKHHKH"), NotificationStatusFeedType{}, None)?;
+            "HHHHHHHHHHHHKKKHHKH"), desc, pos)?;
     Ok(Box::new(file))
 }
 
-pub fn ego_log(client: &mut Client) ->
+pub fn ego_log(file_positions: &HashMap<FeedId, FilePosition>,
+               client: &mut Client) ->
     Result<Box<dyn ActivityState>, ClientError>
 {
+    let feed = FeedId::Ego;
+    let pos = file_positions.get(&feed).cloned();
+    let desc = EgoNotificationFeedType::with_feed(feed.clone());
     let file = File::new(
-        client, FeedSource::new(FeedId::Ego), ColouredString::general(
+        client, FeedSource::new(feed), ColouredString::general(
             "Ego Log   [ESC][L][L][E]",
-            "HHHHHHHHHHHKKKHHKHHKHHKH"), EgoNotificationFeedType{}, None)?;
+            "HHHHHHHHHHHKKKHHKHHKHHKH"), desc, pos)?;
     Ok(Box::new(file))
 }
 
@@ -1196,9 +1253,11 @@ pub fn hashtag_timeline(client: &mut Client, tag: &str) ->
 {
     let title = ColouredString::uniform(
         &format!("Posts mentioning hashtag #{tag}"), 'H');
+    let feed = FeedId::Hashtag(tag.to_owned());
+    let desc = StatusFeedType::with_feed(feed.clone());
     let file = File::new(
         client, FeedSource::new(FeedId::Hashtag(tag.to_owned())), title,
-        StatusFeedType{}, None)?;
+        desc, None)?;
     Ok(Box::new(file))
 }
 
@@ -1292,7 +1351,8 @@ pub fn view_thread(client: &mut Client, start_id: &str, full: bool) ->
         .map_or(isize::MIN, |u| u as isize);
 
     let file = File::new(
-        client, StaticSource::vector(ids), title, StatusFeedType{},
+        client, StaticSource::vector(ids), title,
+        StatusFeedType::without_feed(),
         Some(FilePosition::item_top(index as isize)))?;
     Ok(Box::new(file))
 }
index fa78235580ce92483e68e2dccf8c2e77982224d8..af8d2382e663dc7b12c041f813bc5da067a3fec5 100644 (file)
@@ -11,7 +11,7 @@ use ratatui::{
     style::{Style, Color, Modifier},
 };
 use std::cmp::min;
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::io::{Stdout, Write, stdout};
 use std::fs::File;
 use unicode_width::UnicodeWidthStr;
@@ -415,6 +415,8 @@ pub trait ActivityState {
         LogicalAction;
     fn handle_feed_updates(&mut self, _feeds_updated: &HashSet<FeedId>,
                            _client: &mut Client) {}
+    fn save_file_position(
+        &self, _file_positions: &mut HashMap<FeedId, FilePosition>) {}
 }
 
 struct TuiLogicalState {
@@ -422,6 +424,7 @@ struct TuiLogicalState {
     activity_state: Box<dyn ActivityState>,
     overlay_activity_state: Option<Box<dyn ActivityState>>,
     last_area: Option<Rect>,
+    file_positions: HashMap<FeedId, FilePosition>,
 }
 
 impl TuiLogicalState {
@@ -437,6 +440,7 @@ impl TuiLogicalState {
             activity_state,
             overlay_activity_state: None,
             last_area: None,
+            file_positions: HashMap::new(),
         }
     }
 
@@ -586,6 +590,7 @@ impl TuiLogicalState {
     }
 
     fn changed_activity(&mut self, client: &mut Client, post: Option<Post>) {
+        self.activity_state.save_file_position(&mut self.file_positions);
         self.activity_state = self.new_activity_state(
             self.activity_stack.top(), client, post);
         self.overlay_activity_state = match self.activity_stack.overlay() {
@@ -622,17 +627,17 @@ fn new_activity_state(&self, activity: Activity, client: &mut Client,
             Activity::Util(UtilityActivity::LogsMenu2) =>
                 Ok(logs_menu_2()),
             Activity::NonUtil(NonUtilityActivity::HomeTimelineFile) =>
-                home_timeline(client),
+                home_timeline(&self.file_positions, client),
             Activity::NonUtil(NonUtilityActivity::PublicTimelineFile) =>
-                public_timeline(client),
+                public_timeline(&self.file_positions, client),
             Activity::NonUtil(NonUtilityActivity::LocalTimelineFile) =>
-                local_timeline(client),
+                local_timeline(&self.file_positions, client),
             Activity::NonUtil(NonUtilityActivity::HashtagTimeline(ref id)) =>
                 hashtag_timeline(client, id),
             Activity::Util(UtilityActivity::ReadMentions) =>
-                mentions(client),
+                mentions(&self.file_positions, client),
             Activity::Util(UtilityActivity::EgoLog) =>
-                ego_log(client),
+                ego_log(&self.file_positions, client),
             Activity::Overlay(OverlayActivity::GetUserToExamine) =>
                 Ok(get_user_to_examine()),
             Activity::Overlay(OverlayActivity::GetPostIdToRead) =>