chiark / gitweb /
Now we can read 'messages'! (aka mentions)
authorSimon Tatham <anakin@pobox.com>
Wed, 6 Dec 2023 07:30:03 +0000 (07:30 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 6 Dec 2023 07:30:03 +0000 (07:30 +0000)
client.py
cursesclient.py
text.py

index d46d60be2808ed1306385214d8219cf769e177ee..8e29ae05b1a3afc4513da45d2f7e5d1914092536 100644 (file)
--- a/client.py
+++ b/client.py
@@ -150,6 +150,9 @@ class Client:
     def home_timeline_feed(self):
         return HomeTimelineFeed(self)
 
+    def mentions_feed(self):
+        return MentionsFeed(self)
+
 class Feed:
     """Base class that encapsulates some kind of collection of _things_ we
     can get from the server, with both the ability to go backwards in
@@ -172,7 +175,7 @@ class IncrementalServerFeed(Feed):
     def start(self):
         data, links = self.client.get_incremental_start(
             self.url, **self.params)
-        self.data = list(reversed(data))
+        self.data = list(self.get(d) for d in reversed(data))
         self.origin = len(self.data)
         self.prev_link = links['prev']
         self.next_link = links['next']
@@ -191,7 +194,7 @@ class IncrementalServerFeed(Feed):
         data, links = self.client.get_incremental_cont(self.next_link)
         if len(data) == 0:
             return
-        self.data[0:0] = list(reversed(data))
+        self.data[0:0] = list(self.get(d) for d in reversed(data))
         self.origin += len(data)
         self.next_link = links['next']
 
@@ -201,7 +204,7 @@ class IncrementalServerFeed(Feed):
         data, links = self.client.get_incremental_cont(self.prev_link)
         if len(data) == 0:
             return
-        self.data.extend(reversed(data))
+        self.data.extend(self.get(d) for d in reversed(data))
         self.prev_link = links['prev']
 
 class HomeTimelineFeed(IncrementalServerFeed):
@@ -210,7 +213,8 @@ class HomeTimelineFeed(IncrementalServerFeed):
 
 class MentionsFeed(IncrementalServerFeed):
     def __init__(self, client):
-        super().__init__(client, "notifications", **{"types[]":['mention']})
+        super().__init__(client, "notifications", {"types[]":['mention']},
+                         get=lambda item: item['status'])
 
 class Status:
     def __init__(self, data, client):
index 564a7a0aed7bf91f81879516d503f3f3e0125771..5d2106acf7c3f8445d98099fb0efba5c5904920b 100644 (file)
@@ -140,44 +140,116 @@ class CursesUI(client.Client):
 
     def run(self):
         home_feed = self.home_timeline_feed()
-        self.add_streaming_selfpipe("streaming/user", home_feed.extend_future)
-        self.add_sigwinch_selfpipe()
+        mentions_feed = self.mentions_feed()
+
+        def extend_both():
+            home_feed.extend_future()
+            mentions_feed.extend_future()
+        self.add_streaming_selfpipe("streaming/user", extend_both)
+
         self.home_timeline = StatusFile(
             self, home_feed,
             text.ColouredString("Home timeline   <H>",
                                 "HHHHHHHHHHHHHHHHHKH"))
+        self.mentions_timeline = StatusFile(
+            self, mentions_feed,
+            text.ColouredString("Mentions   [ESC][R]",
+                                "HHHHHHHHHHHHKKKHHKH"))
+
+        self.add_sigwinch_selfpipe()
+
+        self.escape_menu = EscMenu(self)
+
+        self.activity_stack = [self.home_timeline]
 
         try:
             self.curses_setup()
 
             while True:
                 self.clear()
-                self.home_timeline.render()
+                self.activity_stack[-1].render()
                 self.scr.refresh()
                 ch = self.get_input()
-                if ch in {ord(' '), curses.KEY_NPAGE}:
-                    self.home_timeline.down_screen()
-                elif ch in {ord('-'), ord('b'), ord('B'), curses.KEY_PPAGE}:
-                    self.home_timeline.up_screen()
-                elif ch in {13, curses.KEY_DOWN}:
-                    self.home_timeline.down_line()
-                elif ch in {curses.KEY_UP}:
-                    self.home_timeline.up_line()
-                elif ch in {ord('0'), curses.KEY_HOME}:
-                    self.home_timeline.goto_top()
-                elif ch in {ord('z'), ord('Z'), curses.KEY_END}:
-                    self.home_timeline.goto_bottom()
-                elif ch in {ord('q'), ord('Q')}:
-                    break
+                if ch == 27:
+                    if self.activity_stack[-1] is not self.escape_menu:
+                        self.activity_stack.append(self.escape_menu)
                 elif ch == curses.KEY_RESIZE:
                     self.scr_h, self.scr_w = self.scr.getmaxyx()
+                else:
+                    if self.activity_stack[-1].handle_key(ch) == 'quit':
+                        if len(self.activity_stack) > 1:
+                            self.activity_stack.pop()
+                        else:
+                            # Exit whole program when activity stack empties.
+                            # FIXME: do we want to do this, or should the Main
+                            # Menu be sticky at the bottom of the stack and
+                            # require a special ESC X X to exit?
+                            return
 
         finally:
             self.curses_shutdown()
 
-class StatusFile:
-    def __init__(self, cc, feed, title):
+class Menu:
+    def __init__(self, cc):
         self.cc = cc
+
+    def render(self):
+        y = 0
+        header = text.FileHeader(self.title)
+
+        for line in header.render(self.cc.scr_w):
+            self.cc.print_at(y, 0, line)
+            y += 1
+
+        sl = text.FileStatusLine()
+        sl_rendered = util.exactly_one(sl.render(self.cc.scr_w))
+        self.cc.print_at(self.cc.scr_h - 1, 0, sl_rendered)
+
+    def handle_key(self, ch):
+        if ch in {ord('q'), ord('Q'), 13}:
+            return 'quit'
+
+class EscMenu(Menu):
+    def __init__(self, cc):
+        super().__init__(cc)
+        self.title = text.ColouredString(
+            "Utilities [ESC]",
+            "HHHHHHHHHHHKKKH")
+
+    def chain_to(self, activity):
+        assert self.cc.activity_stack[-1] is self
+        self.cc.activity_stack[-1] = activity
+
+    def handle_key(self, ch):
+        if ch in {ord('r'), ord('R')}:
+            self.chain_to(self.cc.mentions_timeline)
+        else:
+            super().handle_key(ch)
+
+class File:
+    # Base class for anything where you page up and down.
+    def __init__(self, cc):
+        self.cc = cc
+
+    def handle_key(self, ch):
+        if ch in {ord(' '), curses.KEY_NPAGE}:
+            self.down_screen()
+        elif ch in {ord('-'), ord('b'), ord('B'), curses.KEY_PPAGE}:
+            self.up_screen()
+        elif ch in {13, curses.KEY_DOWN}:
+            self.down_line()
+        elif ch in {curses.KEY_UP}:
+            self.up_line()
+        elif ch in {ord('0'), curses.KEY_HOME}:
+            self.goto_top()
+        elif ch in {ord('z'), ord('Z'), curses.KEY_END}:
+            self.goto_bottom()
+        elif ch in {ord('q'), ord('Q')}:
+            return 'quit'
+
+class StatusFile(File):
+    def __init__(self, cc, feed, title):
+        super().__init__(cc)
         self.feed = feed
         self.feed.start()
 
diff --git a/text.py b/text.py
index 5b514cbeff0939f0272a566c59a6c378b1e532e4..fd39d4fabac628c4117bf5fb8a1424c18443ddf2 100644 (file)
--- a/text.py
+++ b/text.py
@@ -211,6 +211,8 @@ class FileStatusLine:
             message += sep + ColouredString('({:d}%)'.format(
                 int(self.proportion * 100)))
             sep = sep2
+        elif message.width != 0:
+            message += ColouredString('.')
 
         space = width - message.width
         lspace = space // 2 + 1