home · contact · privacy
Reorganize history log drawing to avoid storing empty lines in history. master
authorChristian Heller <c.heller@plomlompom.de>
Tue, 7 Oct 2025 06:23:25 +0000 (08:23 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 7 Oct 2025 06:23:25 +0000 (08:23 +0200)
src/ircplom/testing.py
src/ircplom/tui_base.py

index ad63e37deff067ad40742012102b05829fbae442..fd63e6dd8f9f724f5e33f25b23275b2220a71679 100644 (file)
@@ -242,6 +242,9 @@ class TestingClientTui(ClientTui):
         self._playbook.put_keypress = self._term._q_keypresses.put
         self._playbook.ensure_has_started()  # if .__init__ didn't yet by log()
 
         self._playbook.put_keypress = self._term._q_keypresses.put
         self._playbook.ensure_has_started()  # if .__init__ didn't yet by log()
 
+    def _switch_window(self, idx: int) -> None:
+        self._window_idx = idx
+
     @classmethod
     def on_file(cls, path_test: Path):
         'Return cls with ._path_test set.'
     @classmethod
     def on_file(cls, path_test: Path):
         'Return cls with ._path_test set.'
index fe74f99cb6efe524e6ec1f515a5e00cb87e36f47..8b5028ee8a87b2393e5f10b20ba9f24d0645cc23 100644 (file)
@@ -115,17 +115,21 @@ class _ScrollableWidget(_Widget):
 
 class _HistoryWidget(_ScrollableWidget):
     _history_idx_neg = -1
 
 class _HistoryWidget(_ScrollableWidget):
     _history_idx_neg = -1
-    _newest_read_history_idx: int = 0
-    _bookmark_history_idx_pos: int = 0
+    _UNSET_HISTORY_IDX_POS: int = -1
+    _COMPARAND_HISTORY_IDX_POS: int = -2
+    _BOOKMARK_HISTORY_IDX_POS: int = -3
+    _PADDING_HISTORY_IDX_POS: int = -4
+    _newest_read_history_idx_pos: int
     _wrapped_idx_neg: int
     _y_pgscroll: int
 
     def __init__(self, wrap: Callable[[str], list[str]], **kwargs) -> None:
         super().__init__(**kwargs)
         self._wrap = wrap
     _wrapped_idx_neg: int
     _y_pgscroll: int
 
     def __init__(self, wrap: Callable[[str], list[str]], **kwargs) -> None:
         super().__init__(**kwargs)
         self._wrap = wrap
-        self._wrapped: list[tuple[Optional[int], str]] = []
+        self._wrapped: list[tuple[int, str]] = []
+        self._newest_read_history_idx_pos = self._UNSET_HISTORY_IDX_POS
 
 
-    def _add_wrapped(self, history_idx_pos, line) -> int:
+    def _add_wrapped(self, history_idx_pos: int, line: str) -> int:
         wrapped_lines = self._wrap(line)
         self._wrapped += [(history_idx_pos, line) for line in wrapped_lines]
         return len(wrapped_lines)
         wrapped_lines = self._wrap(line)
         self._wrapped += [(history_idx_pos, line) for line in wrapped_lines]
         return len(wrapped_lines)
@@ -135,19 +139,16 @@ class _HistoryWidget(_ScrollableWidget):
         if self._drawable:
             self._y_pgscroll = self._sizes.y // 2
             self._wrapped.clear()
         if self._drawable:
             self._y_pgscroll = self._sizes.y // 2
             self._wrapped.clear()
-            self._wrapped_idx_neg = -1
-            self._wrapped += [(None, '')] * self._sizes.y
             for history_idx_pos, line in enumerate(self._history):
                 self._add_wrapped(history_idx_pos, line)
             # ensure that of the full line identified by ._history_idx_neg,
             # ._wrapped_idx_neg point to the lowest of its wrap parts
             for history_idx_pos, line in enumerate(self._history):
                 self._add_wrapped(history_idx_pos, line)
             # ensure that of the full line identified by ._history_idx_neg,
             # ._wrapped_idx_neg point to the lowest of its wrap parts
-            wrapped_lines_for_history_idx = [
-                    t for t in self._wrapped
-                    if t[0] == len(self._history) + self._history_idx_neg]
-            if wrapped_lines_for_history_idx:
-                idx_pos_their_last = self._wrapped.index(
-                        wrapped_lines_for_history_idx[-1])
-                self._wrapped_idx_neg = idx_pos_their_last - len(self._wrapped)
+            self._wrapped_idx_neg = (
+                -1 if (not self._wrapped)
+                else (-len(self._wrapped)
+                      + self._last_wrapped_idx_pos_for_hist_idx_pos(
+                          self._history_idx_neg + len(self._history))))
+            self.bookmark()
 
     def append(self, to_append: str) -> None:
         super().append(to_append)
 
     def append(self, to_append: str) -> None:
         super().append(to_append)
@@ -155,48 +156,63 @@ class _HistoryWidget(_ScrollableWidget):
         if self._history_idx_neg < -1:
             self._history_idx_neg -= 1
         if self._drawable:
         if self._history_idx_neg < -1:
             self._history_idx_neg -= 1
         if self._drawable:
-            n_wrapped_lines = self._add_wrapped(len(self._history)
-                                                - 1, to_append)
+            n_wrapped = self._add_wrapped(len(self._history) - 1, to_append)
             if self._wrapped_idx_neg < -1:
             if self._wrapped_idx_neg < -1:
-                self._wrapped_idx_neg -= n_wrapped_lines
+                self._wrapped_idx_neg -= n_wrapped
 
     def _draw(self) -> None:
 
     def _draw(self) -> None:
-        start_idx_neg = self._wrapped_idx_neg - self._sizes.y + 1
         add_scroll_info = self._wrapped_idx_neg < -1
         add_scroll_info = self._wrapped_idx_neg < -1
-        end_idx_neg = self._wrapped_idx_neg if add_scroll_info else None
-        bookmark_wrapped_idx_pos\
-            = ([idx_pos for idx_pos, t in enumerate(self._wrapped)
-                if t[0] == self._bookmark_history_idx_pos] + [0])[0]
-        wrapped = (self._wrapped[:bookmark_wrapped_idx_pos]
-                   + [(0, '-' * self._sizes.x)]  # bookmark line
-                   + self._wrapped[bookmark_wrapped_idx_pos:])
+        start_idx_neg = (self._wrapped_idx_neg
+                         - self._sizes.y + 1 + bool(add_scroll_info))
+        end_idx_neg = (self._wrapped_idx_neg + 1) if add_scroll_info else None
+
+        wrapped = self._wrapped[start_idx_neg:end_idx_neg]
+        while len(wrapped) < self._sizes.y - bool(add_scroll_info):
+            wrapped.insert(0, (self._PADDING_HISTORY_IDX_POS, ''))
 
         to_write_w_attrs: list[tuple[Optional[str], str]] = []
 
         to_write_w_attrs: list[tuple[Optional[str], str]] = []
-        prev_idx_unwrapped: Optional[int] = -1
+        prev_history_idx_pos = self._COMPARAND_HISTORY_IDX_POS
         attrs: list[str]
         attrs: list[str]
-        for idx_unwrapped, line in wrapped[start_idx_neg:end_idx_neg]:
-            if idx_unwrapped != prev_idx_unwrapped:
+        for history_idx_pos, line in wrapped:
+            if history_idx_pos != prev_history_idx_pos:
                 attrs = ['on_black']
                 for c in line.split(LOG_FMT_SEP, maxsplit=1)[0]:
                     attrs += list(LOG_FMT_ATTRS.get(c, tuple()))
                 attrs = ['on_black']
                 for c in line.split(LOG_FMT_SEP, maxsplit=1)[0]:
                     attrs += list(LOG_FMT_ATTRS.get(c, tuple()))
-                prev_idx_unwrapped = idx_unwrapped
+                prev_history_idx_pos = history_idx_pos
             to_write_w_attrs += [(','.join(attrs), line)]
 
         if add_scroll_info:
             to_write_w_attrs += [(','.join(attrs), line)]
 
         if add_scroll_info:
-            scroll_info = f'vvv [{(-1) * self._wrapped_idx_neg}] '
+            scroll_info = f'vvv [{(-1) * self._history_idx_neg}] '
             scroll_info += 'v' * (self._sizes.x - len(scroll_info))
             to_write_w_attrs += [('reverse', scroll_info)]
 
         for idx, line_t in enumerate(to_write_w_attrs):
             self._write(start_y=idx, attributes=line_t[0], msg=line_t[1])
 
             scroll_info += 'v' * (self._sizes.x - len(scroll_info))
             to_write_w_attrs += [('reverse', scroll_info)]
 
         for idx, line_t in enumerate(to_write_w_attrs):
             self._write(start_y=idx, attributes=line_t[0], msg=line_t[1])
 
-        hist_idx_pos = self._wrapped[(end_idx_neg or 0) - 1][0] or 0
-        self._newest_read_history_idx = max(self._newest_read_history_idx,
-                                            hist_idx_pos)
+        self._newest_read_history_idx_pos\
+            = max(self._newest_read_history_idx_pos, wrapped[-1][0])
 
     def bookmark(self) -> None:
 
     def bookmark(self) -> None:
-        'Store to what most recent line we have (been) scrolled.'
-        self._bookmark_history_idx_pos = self._newest_read_history_idx + 1
+        'Store next idx to what most recent line we have (been) scrolled.'
+        bookmark = (self._BOOKMARK_HISTORY_IDX_POS, '-' * self._sizes.x)
+        if bookmark in self._wrapped:
+            bookmark_idx_neg\
+                = self._wrapped.index(bookmark) - len(self._wrapped)
+            del self._wrapped[bookmark_idx_neg]
+            if bookmark_idx_neg > self._wrapped_idx_neg:
+                self._wrapped_idx_neg += 1
+        bookmark_wrapped_idx_pos = (
+                0 if (not self._wrapped)
+                else (1 + self._last_wrapped_idx_pos_for_hist_idx_pos(
+                    self._newest_read_history_idx_pos)))
+        self._wrapped.insert(bookmark_wrapped_idx_pos, bookmark)
+        if bookmark_wrapped_idx_pos - len(self._wrapped)\
+                > self._wrapped_idx_neg:
+            self._wrapped_idx_neg -= 1
+
+    def _last_wrapped_idx_pos_for_hist_idx_pos(self, hist_idx_pos: int) -> int:
+        return [idx for idx, t in enumerate(self._wrapped)
+                if t[0] == hist_idx_pos][-1]
 
     @property
     def has_unread_highlight(self) -> bool:
 
     @property
     def has_unread_highlight(self) -> bool:
@@ -207,22 +223,21 @@ class _HistoryWidget(_ScrollableWidget):
     @property
     def n_lines_unread(self) -> int:
         'How many new lines have been logged since last focus.'
     @property
     def n_lines_unread(self) -> int:
         'How many new lines have been logged since last focus.'
-        return len(self._history) - self._newest_read_history_idx - 1
+        return len(self._history) - (self._newest_read_history_idx_pos + 1)
 
     def _scroll(self, up: bool = True) -> None:
         super()._scroll(up)
 
     def _scroll(self, up: bool = True) -> None:
         super()._scroll(up)
-        if self._drawable:
+        if self._drawable and self._wrapped:
             if up:
                 self._wrapped_idx_neg = max(
             if up:
                 self._wrapped_idx_neg = max(
-                        self._sizes.y + 1 - len(self._wrapped),
+                        -len(self._wrapped),
                         self._wrapped_idx_neg - self._y_pgscroll)
             else:
                 self._wrapped_idx_neg = min(
                         -1, self._wrapped_idx_neg + self._y_pgscroll)
                         self._wrapped_idx_neg - self._y_pgscroll)
             else:
                 self._wrapped_idx_neg = min(
                         -1, self._wrapped_idx_neg + self._y_pgscroll)
-            hist_idx_to_wrapped_idx = self._wrapped[self._wrapped_idx_neg][0]
-            if hist_idx_to_wrapped_idx is not None:
-                self._history_idx_neg = hist_idx_to_wrapped_idx\
-                        - len(self._history)
+            self._history_idx_neg\
+                = (-len(self._history)
+                   + max(0, self._wrapped[self._wrapped_idx_neg][0]))
 
 
 class PromptWidget(_ScrollableWidget):
 
 
 class PromptWidget(_ScrollableWidget):