chiark / gitweb /
Paging around a file now works!
authorSimon Tatham <anakin@pobox.com>
Tue, 5 Dec 2023 13:05:23 +0000 (13:05 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 5 Dec 2023 13:05:23 +0000 (13:05 +0000)
cursesclient.py
text.py
util.py

index 837edb6e1fc2b6fd15ee2239392c979b2d148b7a..489e5e9cac4e84f4ea334072002719aae1c067c8 100644 (file)
@@ -4,6 +4,7 @@ import sys
 
 import client
 import text
+import util
 
 class CursesUI(client.Client):
     def curses_setup(self):
@@ -83,6 +84,10 @@ class CursesUI(client.Client):
         for y in range(self.scr_h):
             self.print_at(y, 0, text.ColouredString(' ' * self.scr_w))
 
+    def get_input(self):
+        # FIXME: add a select loop for self-pipes
+        return self.scr.getch()
+
     def run(self):
         self.home_timeline = StatusFile(
             self, self.home_timeline_feed(),
@@ -91,9 +96,29 @@ class CursesUI(client.Client):
 
         try:
             self.curses_setup()
-            self.home_timeline.render()
-            self.scr.refresh()
-            self.scr.getch()
+
+            while True:
+                self.clear()
+                self.home_timeline.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
+                elif ch == curses.KEY_RESIZE:
+                    self.scr_h, self.scr_w = self.scr.getmaxyx()
+
         finally:
             self.curses_shutdown()
 
@@ -114,6 +139,7 @@ class StatusFile:
 
         self.lines = None
         self.linepos = None
+        self.width = None
 
     def iter_text_indexed(self):
         yield self.header, None
@@ -124,32 +150,46 @@ class StatusFile:
                 yield thing, i # FIXME: maybe just yield the last?
 
     def resize(self, width):
+        if self.width == width:
+            return
+        self.width = width
         self.lines = []
-        self.linepos = 0
+        pos = 0
         for thing, itemindex in self.iter_text_indexed():
             for line in thing.render(width):
-                self.lines.append(line)
+                for s in line.split(width):
+                    self.lines.append(s)
             if itemindex == self.itempos:
-                self.linepos = len(self.lines)
+                pos = len(self.lines)
+        self.move_to(pos)
 
     def render(self):
-        self.cc.clear()
         self.resize(self.cc.scr_w)
-        topline = max(0, self.linepos - self.cc.scr_w - 1)
+        topline = max(0, self.linepos - (self.cc.scr_h - 1))
         for y, line in enumerate(self.lines[topline:topline+self.cc.scr_h-1]):
             self.cc.print_at(y, 0, line)
 
         sl = text.FileStatusLine()
-        sl.percentage = self.linepos / len(self.lines)
-        sl_rendered = next(sl.render(self.cc.scr_w))
-        # FIXME: keys
+        if self.linepos >= len(self.lines):
+            sl.keys.append(('-', 'Up'))
+        else:
+            sl.keys.append(('SPACE', 'More'))
+        sl.keys.append(('Q', 'Exit'))
+        sl.proportion = self.linepos / len(self.lines)
+        sl_rendered = util.exactly_one(sl.render(self.cc.scr_w))
         self.cc.print_at(self.cc.scr_h - 1, 0, sl_rendered)
 
-class Activity:
-    pass
+    def move_to(self, pos):
+        self.linepos = pos
+        self.linepos = max(self.linepos, self.cc.scr_h - 1)
+        self.linepos = min(self.linepos, len(self.lines))
 
-class FileActivity(Activity):
-    pass
+    def move_by(self, delta):
+        self.move_to(self.linepos + delta)
 
-class StatusFileActivity(FileActivity):
-    pass
+    def down_screen(self): self.move_by(self.cc.scr_h - 1)
+    def up_screen(self): self.move_by(-(self.cc.scr_h - 1))
+    def down_line(self): self.move_by(+1)
+    def up_line(self): self.move_by(-1)
+    def goto_top(self): self.move_to(0)
+    def goto_bottom(self): self.move_to(len(self.lines))
diff --git a/text.py b/text.py
index 4448548d58fd9b9e359638e204e5d37b76f77136..c5e81833b4909b3138deeb3cc019e8edf603cbc4 100644 (file)
--- a/text.py
+++ b/text.py
@@ -94,6 +94,16 @@ class ColouredString:
             yield frag, colour, wcwidth.wcswidth(frag)
             pos += fraglen
 
+    def split(self, width):
+        # Split on to multiple physical lines.
+        line = ColouredString("")
+        for c in self:
+            if line.width + c.width > width:
+                yield line
+                line = ColouredString("")
+            line += c
+        yield line
+
 class BlankLine:
     def render(self, width):
         yield ColouredString("")
@@ -161,14 +171,15 @@ class FileHeader:
         # FIXME: truncate
         headertext = self.text
 
-        space = width - 2 * logowidth - 2 - headertext.width
-        lspace = space // 2 + 1
-        rspace = space - lspace + 1
+        midspace = width - 1 - 2 * logowidth - 2
+        space = midspace - headertext.width
+        lspace = space // 2
+        rspace = space - lspace
 
-        yield (logo[0] + ColouredString(" " * lspace) +
-               headertext + ColouredString(" " * rspace) + logo[0])
+        yield (logo[0] + ColouredString(" " * (lspace+1)) +
+               headertext + ColouredString(" " * (rspace+1)) + logo[0])
         yield (logo[1] + ColouredString(" ") +
-               ColouredString("~" * space, '~') + ColouredString(" ") +
+               ColouredString("~" * midspace, '~') + ColouredString(" ") +
                logo[1])
 
 class ExtendableIndicator:
@@ -191,24 +202,22 @@ class FileStatusLine:
     def render(self, width):
         message = ColouredString('')
         sep = ColouredString('')
+        sep2 = ColouredString('  ')
         for key, action in self.keys:
             message += (
                 sep + ColouredString('[') + ColouredString(key, 'k') +
                 ColouredString(']:' + action))
-            sep = ColouredString('  ')
+            sep = sep2
         if self.proportion is not None:
-            message += (
-                sep + ColouredString('({:d}%)'.format(self.proportion * 100)))
-            sep = ColouredString('  ')
+            message += sep + ColouredString('({:d}%)'.format(
+                int(self.proportion * 100)))
+            sep = sep2
 
         space = width - message.width
         lspace = space // 2 + 1
         rspace = space - lspace + 1
 
-        yield ColouredString("")
-        yield (ColouredString(" " * lspace) + message +
-               ColouredString(" " * rspace))
-        yield ColouredString("")
+        yield (ColouredString(" " * lspace) + message)
 
 class Paragraph:
     def __init__(self):
diff --git a/util.py b/util.py
index dcec58b96c36834a6ea5d11cfda800fdd4aa1ed0..fcd6578a34b8d55a3615af7e75fcbb6507a11580 100644 (file)
--- a/util.py
+++ b/util.py
@@ -13,3 +13,12 @@ class SelfPipe:
         while len(os.read(self, 4096)) != 0:
             pass
         return True
+
+def exactly_one(stuff):
+    it = iter(stuff)
+    toret = next(it)
+    try:
+        next(it)
+    except StopIteration:
+        return toret
+    raise ValueError("exactly_one got more than one")