home · contact · privacy
Refactor Terminal.write_yx() into more flexible Terminal.write().
authorChristian Heller <c.heller@plomlompom.de>
Mon, 9 Jun 2025 13:37:06 +0000 (15:37 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 9 Jun 2025 13:37:06 +0000 (15:37 +0200)
ircplom.py

index 5a8565660b5a8931ee82e545e40c646c36628a40..bffd3c21af87a0af4fa9eb924bfbffc4acf74cd0 100755 (executable)
@@ -77,6 +77,7 @@ class Terminal:
     size: YX
     tui: 'TuiLoop'
     _blessed: BlessedTerminal
+    _cursor_yx_: YX
 
     @contextmanager
     def context(self, q_to_main: EventQueue) -> Generator:
@@ -87,9 +88,19 @@ class Terminal:
               self._blessed.fullscreen(),
               self._blessed.hidden_cursor(),
               KeyboardLoop(q_to_main, self.get_keypresses())):
+            self._cursor_yx = YX(0, 0)
             with TuiLoop(self, q_to_main) as self.tui:
                 yield self
 
+    @property
+    def _cursor_yx(self) -> YX:
+        return self._cursor_yx_
+
+    @_cursor_yx.setter
+    def _cursor_yx(self, yx: YX) -> None:
+        print(self._blessed.move_yx(yx.y, yx.x), end='')
+        self._cursor_yx_ = yx
+
     def calc_geometry(self) -> None:
         '(Re-)calculate .size..'
         self.size = YX(self._blessed.height, self._blessed.width)
@@ -107,17 +118,28 @@ class Terminal:
         return self._blessed.wrap(line, width=self.size.x,
                                   subsequent_indent=' '*4)
 
-    def write_yx(self, offset: YX, msg: str) -> None:
-        'Starting at offset, write line with msg, padded at end with spaces.'
-        print(self._blessed.move_yx(offset.y, offset.x), end='')
-        # ._blessed.length can slow down things notably, only use where needed
-        len_with_offset = offset.x + (len(msg) if msg.isascii()
-                                      else self._blessed.length(msg))
-        if len_with_offset > self.size.x:
-            print(self._blessed.truncate(msg, self.size.x - offset.x), end='')
-        else:
-            len_padding = self.size.x - len_with_offset
-            print(msg + (' ' * len_padding), end='')
+    def write(self,
+              msg: str = '',
+              start_y: Optional[int] = None,
+              attribute: Optional[str] = None,
+              padding: bool = True
+              ) -> None:
+        'Print to terminal, with position, padding to line end, attributes.'
+        if start_y:
+            self._cursor_yx = YX(start_y, 0)
+        # ._blessed.length can slow down things notably: only use where needed!
+        end_x = self._cursor_yx.x + (len(msg) if msg.isascii()
+                                     else self._blessed.length(msg))
+        len_padding = self.size.x - end_x
+        if len_padding < 0:
+            msg = self._blessed.truncate(msg, self.size.x - self._cursor_yx.x)
+        elif padding:
+            msg += ' ' * len_padding
+            end_x = self.size.x
+        if attribute:
+            msg = getattr(self._blessed, attribute)(msg)
+        print(msg, end='')
+        self._cursor_yx = YX(self._cursor_yx.y, end_x)
 
     def get_keypresses(self) -> Iterator[str]:
         '''Loop through keypresses from terminal, collect what blessed ignores.
@@ -466,8 +488,9 @@ class ScrollableWidget(Widget):
     'Defines some API shared between PromptWidget and LogWidget.'
     _history_idx: int
 
-    def __init__(self, write_yx: Callable[[YX, str], None]) -> None:
-        self._write_yx = write_yx
+    def __init__(self, write: Callable[..., None], *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+        self._write = write
         self._history: list[str] = []
 
     @abstractmethod
@@ -514,7 +537,7 @@ class PromptWidget(ScrollableWidget):
                 break
             offset += len_too_much
             prompt = f'<{offset}|{prompt_template}…'
-        self._write_yx(YX(self._y, 0), to_write)
+        self._write(to_write, self._y)
 
     def _scroll(self, up: bool = True) -> None:
         if up and -(self._history_idx) < len(self._history):
@@ -604,7 +627,7 @@ class LogWidget(ScrollableWidget):
         else:
             to_write += [self._wrapped[self._wrapped_idx][1]]
         for i, line in enumerate(to_write):
-            self._write_yx(YX(i, 0), line)
+            self._write(line, i)
 
     def _scroll(self, up: bool = True) -> None:
         if up:
@@ -625,8 +648,8 @@ class Window(Widget):
     def __init__(self, idx: int, term: Terminal) -> None:
         self.idx = idx
         self._term = term
-        self.log = LogWidget(self._term.wrap, self._term.write_yx)
-        self.prompt = PromptWidget(self._term.write_yx)
+        self.log = LogWidget(self._term.wrap, self._term.write)
+        self.prompt = PromptWidget(self._term.write)
         if hasattr(self._term, 'size'):
             self.set_geometry()
 
@@ -641,12 +664,12 @@ class Window(Widget):
         status_line = idx_box + '=' * (self._term.size.x - len(idx_box))
         self._term.clear()
         self.log.draw()
-        self._term.write_yx(YX(self._y_status, 0), status_line)
+        self._term.write(status_line, self._y_status)
         self.prompt.draw()
 
     def cmd__paste(self) -> None:
         'Write OSC 52 ? sequence to get encoded clipboard paste into stdin.'
-        self._term.write_yx(YX(self._y_status, 0), f'\033{OSC_52_PREFIX}?\007')
+        self._term.write(f'\033{OSC_52_PREFIX}?\007', self._y_status)
         self.draw()