From 6342a54ef80a3578286f40855ce3f52b06800545 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Dec 2023 09:30:09 +0000 Subject: [PATCH] Ability to extend the feed backwards in time. Haven't yet tested the part where extension runs out, though. That will be easier when we get some more kinds of feed, so I've left a FIXME comment in there. --- src/client.rs | 23 ++++++++--- src/file.rs | 112 ++++++++++++++++++++++++++++++++++++++++---------- src/tui.rs | 4 +- 3 files changed, 111 insertions(+), 28 deletions(-) diff --git a/src/client.rs b/src/client.rs index b9393d4..85fded5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -19,6 +19,7 @@ pub enum FeedId { User(String, Boosts, Replies), } +#[derive(Debug)] pub struct Feed { pub ids: VecDeque, // ids, whether of statuses, accounts or what pub origin: isize, @@ -33,7 +34,7 @@ pub struct Client { permit_write: bool, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum ClientError { InternalError(String), // message UrlParseError(String, String), // url, message @@ -228,13 +229,22 @@ impl Client { Ok(st) } + // Ok(bool) tells you whether any new items were in fact retrieved pub fn fetch_feed(&mut self, id: &FeedId, ext: FeedExtend) -> - Result<(), ClientError> + Result { if ext == FeedExtend::Initial { if self.feeds.contains_key(id) { - // No need to fetch the initial contents - we already have some - return Ok(()); + // No need to fetch the initial contents - we already + // have some. + // + // In this situation the boolean return value is not + // expected to very useful: its main purpose is when + // finding out if extending an _existing_ feed did + // anything. But we might as well return false in the + // case where nothing changed, and true in the case + // below where we did end up retrieving something. + return Ok(false); } } else { assert!(self.feeds.contains_key(id), @@ -284,6 +294,7 @@ impl Client { Err(ClientError::UrlError(url.clone(), e.to_string())) }, }?; + let any_new = !sts.is_empty(); for st in &sts { self.cache_status(st); } @@ -305,12 +316,12 @@ impl Client { let feed = self.feeds.get_mut(id).unwrap(); for id in ids.iter().rev() { feed.ids.push_front(id.to_string()); - feed.origin += 1; + feed.origin -= 1; } }, } - Ok(()) + Ok(any_new) } pub fn borrow_feed(&self, id: &FeedId) -> &Feed { diff --git a/src/file.rs b/src/file.rs index b08e32e..181d4c8 100644 --- a/src/file.rs +++ b/src/file.rs @@ -18,14 +18,14 @@ enum FilePosition { struct FeedFileContents { id: FeedId, header: FileHeader, + extender: Option, origin: isize, items: Vec>, } impl FeedFileContents { fn update_items(&mut self, client: &mut Client) { - // FIXME: deal with origin, and with the feed having been - // extended since we last looked + // FIXME: deal with the feed having been extended let feed = client.borrow_feed(&self.id); let ids: Vec<_> = feed.ids.iter().map(|x| x.clone()).collect(); @@ -40,7 +40,13 @@ impl FeedFileContents { } } - fn first_index(&self) -> isize { self.origin - 1 } + fn first_index(&self) -> isize { + let extcount = if self.extender.is_some() { 1 } else { 0 }; + self.origin - 1 - extcount + } + fn extender_index(&self) -> Option { + if self.extender.is_some() { Some(self.origin - 1) } else { None } + } fn index_limit(&self) -> isize { self.origin.checked_add_unsigned(self.items.len()) .expect("Out-of-range index") @@ -53,8 +59,13 @@ impl FeedFileContents { } fn get(&self, index: isize) -> &dyn TextFragment { - if index == self.origin - 1 { + if index == self.first_index() { &self.header + } else if Some(index) == self.extender_index() { + match self.extender { + Some(ref ext) => ext, + _ => panic!("But it must be there, extender_index exists"), + } } else { let index = self.phys_index(index); &*self.items[index] @@ -78,6 +89,7 @@ impl FeedFile { let mut contents = FeedFileContents { id: id, header: FileHeader::new(desc), + extender: Some(ExtendableIndicator::new()), origin: 0, items: Vec::new(), }; @@ -114,7 +126,7 @@ impl FeedFile { self.rendered.get(&index).expect("We just made sure this was present") } - fn ensure_enough_rendered(&mut self) -> usize { + fn ensure_enough_rendered(&mut self) -> Option { let (w, h) = self.last_size.expect( "ensure_enough_rendered before resize"); @@ -122,12 +134,32 @@ impl FeedFile { let mut item = item; let mut lines_rendered = line; - while item > self.contents.first_index() && lines_rendered + 1 < h { + while item > self.contents.first_index() && lines_rendered < h { item -= 1; lines_rendered += self.ensure_item_rendered(item, w).len(); } - h - min(h, lines_rendered + 1) + if lines_rendered + 1 <= h { + Some(h - 1 - lines_rendered) + } else { + None + } + } + + fn after_setting_pos(&mut self) { + let (w, _h) = self.last_size.expect( + "ensure_enough_rendered before setting pos"); + let at_top = self.at_top(); + match &mut self.contents.extender { + Some(ref mut ext) => { + ext.set_primed(at_top); + }, + _ => (), + }; + if let Some(ei) = self.contents.extender_index() { + self.rendered.remove(&ei); + self.ensure_item_rendered(ei, w); + } } fn refine_pos(&mut self, w: usize) -> (isize, usize) { @@ -153,6 +185,20 @@ impl FeedFile { self.pos = newpos; } + fn fix_overshoot_at_top(&mut self) { + // If an attempt to move up the document left us with no line + // to display at the top of the screen, move down again to fix + // it + if let Some(overshoot) = self.ensure_enough_rendered() { + // Clip position at top of file + if overshoot > 0 { + self.move_down_inner(overshoot); + } + } + } + + 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 { @@ -176,14 +222,11 @@ impl FeedFile { } } self.pos = FilePosition::Fine(item, line); - let overshoot = self.ensure_enough_rendered(); - // Clip position at top of file - if overshoot > 0 { - self.move_down(overshoot); - } + self.fix_overshoot_at_top(); + self.after_setting_pos(); } - fn move_down(&mut self, distance: usize) { + 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), @@ -213,13 +256,15 @@ impl FeedFile { self.ensure_enough_rendered(); } + fn move_down(&mut self, distance: usize) { + self.move_down_inner(distance); + self.after_setting_pos(); + } + fn goto_top(&mut self) { self.pos = FilePosition::Fine(self.contents.first_index(), 0); - let overshoot = self.ensure_enough_rendered(); - // Clip position at top of file - if overshoot > 0 { - self.move_down(overshoot); - } + self.fix_overshoot_at_top(); + self.after_setting_pos(); } fn goto_bottom(&mut self) { @@ -228,6 +273,7 @@ impl FeedFile { let line = self.ensure_item_rendered(item, w).len(); self.pos = FilePosition::Fine(item, line); self.ensure_enough_rendered(); + self.after_setting_pos(); } } @@ -239,6 +285,7 @@ impl ActivityState for FeedFile { self.rendered.clear(); } self.ensure_enough_rendered(); + self.after_setting_pos(); } fn draw(&self, w: usize, h: usize) @@ -298,7 +345,7 @@ impl ActivityState for FeedFile { (lines, CursorPosition::End) } - fn handle_keypress(&mut self, key: OurKey, _client: &mut Client) -> + fn handle_keypress(&mut self, key: OurKey, client: &mut Client) -> LogicalAction { let (_w, h) = match self.last_size { @@ -334,8 +381,31 @@ impl ActivityState for FeedFile { LogicalAction::Nothing }, Pr('0') | Home => { - self.goto_top(); - LogicalAction::Nothing + if self.at_top() && self.contents.extender.is_some() { + match client.fetch_feed(&self.contents.id, + FeedExtend::Past) { + Ok(any_new) => { + self.rendered.remove(&self.contents.first_index()); + if let Some(i) = self.contents.extender_index() { + self.rendered.remove(&i); + } + + if !any_new { + // Can't extend this any further into the past + // FIXME: this is not tested yet + self.contents.extender = None; + } + + self.contents.update_items(client); + self.ensure_enough_rendered(); + LogicalAction::Nothing + }, + Err(e) => LogicalAction::Error(e), + } + } else { + self.goto_top(); + LogicalAction::Nothing + } } Pr('z') | Pr('Z') | End => { self.goto_bottom(); diff --git a/src/tui.rs b/src/tui.rs index 8442b96..beaa956 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -14,7 +14,7 @@ use std::io::{Stdout, Write, stdout}; use unicode_width::UnicodeWidthStr; use super::activity_stack::*; -use super::client::Client; +use super::client::{Client, ClientError}; use super::coloured_string::{ColouredString, ColouredStringSlice}; use super::menu::*; use super::file::*; @@ -337,6 +337,7 @@ pub enum LogicalAction { Goto(Activity), Exit, Nothing, + Error(ClientError), // throw UI into the Error Log NYI, // FIXME: get rid of this once everything is implemented } @@ -446,6 +447,7 @@ impl TuiLogicalState { self.changed_activity(client); PhysicalAction::Nothing }, + LogicalAction::Error(_) => PhysicalAction::Beep, // FIXME: Error Log } } -- 2.30.2