home
·
contact
·
privacy
projects
/
ircplom
/ commitdiff
commit
grep
author
committer
pickaxe
?
search:
re
summary
|
shortlog
|
log
|
commit
| commitdiff |
tree
raw
|
patch
|
inline
| side by side (parent:
7dd4da6
)
Reorganize how TUI determines what parts of screen to redraw.
master
author
Christian Heller
<c.heller@plomlompom.de>
Sun, 15 Jun 2025 18:22:44 +0000
(20:22 +0200)
committer
Christian Heller
<c.heller@plomlompom.de>
Sun, 15 Jun 2025 18:22:44 +0000
(20:22 +0200)
ircplom/tui.py
patch
|
blob
|
history
diff --git
a/ircplom/tui.py
b/ircplom/tui.py
index f36ff511efaaecc03d0cac9e80a8594dcb38e99a..61589b8464201da28ff3c35e02d9c0fbce547c32 100644
(file)
--- a/
ircplom/tui.py
+++ b/
ircplom/tui.py
@@
-68,13
+68,19
@@
class _YX(NamedTuple):
class _Widget(ABC):
'Defines most basic TUI object API.'
class _Widget(ABC):
'Defines most basic TUI object API.'
+ @abstractmethod
+ def __init__(self, *args, **kwargs) -> None:
+ self.tainted = True
+
@abstractmethod
def set_geometry(self, measurements: _YX) -> None:
'Update widget\'s measurements, re-generate content where necessary.'
@abstractmethod
def set_geometry(self, measurements: _YX) -> None:
'Update widget\'s measurements, re-generate content where necessary.'
+ self.tainted = True
@abstractmethod
def draw(self) -> None:
'Print widget\'s content in shape appropriate to set geometry.'
@abstractmethod
def draw(self) -> None:
'Print widget\'s content in shape appropriate to set geometry.'
+ self.tainted = False
class _ScrollableWidget(_Widget, ABC):
class _ScrollableWidget(_Widget, ABC):
@@
-92,12
+98,11
@@
class _ScrollableWidget(_Widget, ABC):
@abstractmethod
def _scroll(self, up=True) -> None:
@abstractmethod
def _scroll(self, up=True) -> None:
- pass
+ self.tainted = True
def cmd__scroll(self, direction: str) -> None:
'Scroll through stored content/history.'
self._scroll(up=direction == 'up')
def cmd__scroll(self, direction: str) -> None:
'Scroll through stored content/history.'
self._scroll(up=direction == 'up')
- self.draw()
class _LogWidget(_ScrollableWidget):
class _LogWidget(_ScrollableWidget):
@@
-118,6
+123,7
@@
class _LogWidget(_ScrollableWidget):
return len(wrapped_lines)
def set_geometry(self, measurements: _YX) -> None:
return len(wrapped_lines)
def set_geometry(self, measurements: _YX) -> None:
+ super().set_geometry(measurements)
self._view_size = measurements
self._y_pgscroll = self._view_size.y // 2
self._wrapped.clear()
self._view_size = measurements
self._y_pgscroll = self._view_size.y // 2
self._wrapped.clear()
@@
-138,8
+144,10
@@
class _LogWidget(_ScrollableWidget):
if self._wrapped_idx < -1:
self._history_idx -= 1
self._wrapped_idx -= n_wrapped_lines
if self._wrapped_idx < -1:
self._history_idx -= 1
self._wrapped_idx -= n_wrapped_lines
+ self.tainted = True
def draw(self) -> None:
def draw(self) -> None:
+ super().draw()
start_idx = self._wrapped_idx - self._view_size.y + 1
end_idx = self._wrapped_idx
to_write = [t[1] for t in self._wrapped[start_idx:end_idx]]
start_idx = self._wrapped_idx - self._view_size.y + 1
end_idx = self._wrapped_idx
to_write = [t[1] for t in self._wrapped[start_idx:end_idx]]
@@
-153,6
+161,7
@@
class _LogWidget(_ScrollableWidget):
self._write(line, i)
def _scroll(self, up: bool = True) -> None:
self._write(line, i)
def _scroll(self, up: bool = True) -> None:
+ super()._scroll(up)
if up:
self._wrapped_idx = max(self._view_size.y + 1 - len(self._wrapped),
self._wrapped_idx - self._y_pgscroll)
if up:
self._wrapped_idx = max(self._view_size.y + 1 - len(self._wrapped),
self._wrapped_idx - self._y_pgscroll)
@@
-170,19
+179,30
@@
class _PromptWidget(_ScrollableWidget):
_width: int
_prompt: str = _PROMPT_TEMPLATE
_history_idx = 0
_width: int
_prompt: str = _PROMPT_TEMPLATE
_history_idx = 0
- _input_buffer: str
+ _input_buffer
_unsafe
: str
_cursor_x: int
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._reset_buffer('')
_cursor_x: int
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._reset_buffer('')
+ @property
+ def _input_buffer(self) -> str:
+ return self._input_buffer_unsafe[:]
+
+ @_input_buffer.setter
+ def _input_buffer(self, content) -> None:
+ self.tainted = True
+ self._input_buffer_unsafe = content
+
def set_geometry(self, measurements: _YX) -> None:
def set_geometry(self, measurements: _YX) -> None:
+ super().set_geometry(measurements)
self._y, self._width = measurements
def draw(self) -> None:
self._y, self._width = measurements
def draw(self) -> None:
+ super().draw()
prefix = self._prompt[:]
prefix = self._prompt[:]
- content = self._input_buffer
[:]
+ content = self._input_buffer
if self._cursor_x == len(self._input_buffer):
content += ' '
half_width = (self._width - len(prefix)) // 2
if self._cursor_x == len(self._input_buffer):
content += ' '
half_width = (self._width - len(prefix)) // 2
@@
-203,10
+223,11
@@
class _PromptWidget(_ScrollableWidget):
self._write(to_write[cursor_x_to_write + 1:])
def _archive_prompt(self) -> None:
self._write(to_write[cursor_x_to_write + 1:])
def _archive_prompt(self) -> None:
- self.append(self._input_buffer
[:]
)
+ self.append(self._input_buffer)
self._reset_buffer('')
def _scroll(self, up: bool = True) -> None:
self._reset_buffer('')
def _scroll(self, up: bool = True) -> None:
+ super()._scroll(up)
if up and -(self._history_idx) < len(self._history):
if self._history_idx == 0 and self._input_buffer:
self._archive_prompt()
if up and -(self._history_idx) < len(self._history):
if self._history_idx == 0 and self._input_buffer:
self._archive_prompt()
@@
-230,7
+251,6
@@
class _PromptWidget(_ScrollableWidget):
+ to_append
+ self._input_buffer[self._cursor_x - 1:])
self._history_idx = 0
+ to_append
+ self._input_buffer[self._cursor_x - 1:])
self._history_idx = 0
- self.draw()
def cmd__backspace(self) -> None:
'Truncate current content by one character, if possible.'
def cmd__backspace(self) -> None:
'Truncate current content by one character, if possible.'
@@
-239,7
+259,6
@@
class _PromptWidget(_ScrollableWidget):
self._input_buffer = (self._input_buffer[:self._cursor_x]
+ self._input_buffer[self._cursor_x + 1:])
self._history_idx = 0
self._input_buffer = (self._input_buffer[:self._cursor_x]
+ self._input_buffer[self._cursor_x + 1:])
self._history_idx = 0
- self.draw()
def cmd__move_cursor(self, direction: str) -> None:
'Move cursor one space into direction ("left" or "right") if possible.'
def cmd__move_cursor(self, direction: str) -> None:
'Move cursor one space into direction ("left" or "right") if possible.'
@@
-250,7
+269,7
@@
class _PromptWidget(_ScrollableWidget):
self._cursor_x += 1
else:
return
self._cursor_x += 1
else:
return
- self.
draw()
+ self.
tainted = True
def _reset_buffer(self, content: str) -> None:
self._input_buffer = content
def _reset_buffer(self, content: str) -> None:
self._input_buffer = content
@@
-261,7
+280,6
@@
class _PromptWidget(_ScrollableWidget):
to_return = self._input_buffer[:]
if to_return:
self._archive_prompt()
to_return = self._input_buffer[:]
if to_return:
self._archive_prompt()
- self.draw()
return to_return
return to_return
@@
-274,6
+292,7
@@
class _ConnectionPromptWidget(_PromptWidget):
nick: Optional[str] = None
) -> None:
'Update nickname-relevant knowledge to go into prompt string.'
nick: Optional[str] = None
) -> None:
'Update nickname-relevant knowledge to go into prompt string.'
+ self.tainted = True
self._prompt = ''
if nick:
self._nickname = nick
self._prompt = ''
if nick:
self._nickname = nick
@@
-298,12
+317,14
@@
class _Window(_Widget):
self.set_geometry()
def set_geometry(self, _=None) -> None:
self.set_geometry()
def set_geometry(self, _=None) -> None:
+ super().set_geometry(_)
assert _ is None
self._y_status = self._term.size.y - 2
self.log.set_geometry(_YX(self._y_status, self._term.size.x))
self.prompt.set_geometry(_YX(self._term.size.y - 1, self._term.size.x))
def draw(self) -> None:
assert _ is None
self._y_status = self._term.size.y - 2
self.log.set_geometry(_YX(self._y_status, self._term.size.x))
self.prompt.set_geometry(_YX(self._term.size.y - 1, self._term.size.x))
def draw(self) -> None:
+ super().draw()
idx_box = f'[{self.idx}]'
status_line = idx_box + '=' * (self._term.size.x - len(idx_box))
self._term.clear()
idx_box = f'[{self.idx}]'
status_line = idx_box + '=' * (self._term.size.x - len(idx_box))
self._term.clear()
@@
-315,7
+336,15
@@
class _Window(_Widget):
'Write OSC 52 ? sequence to get encoded clipboard paste into stdin.'
self._term.write(f'\033{_OSC52_PREFIX}?{_PASTE_DELIMITER}',
self._y_status)
'Write OSC 52 ? sequence to get encoded clipboard paste into stdin.'
self._term.write(f'\033{_OSC52_PREFIX}?{_PASTE_DELIMITER}',
self._y_status)
- self.draw()
+ self.tainted = True
+
+ def draw_tainted(self) -> None:
+ 'Draw tainted parts of self.'
+ if self.tainted:
+ self.draw()
+ return
+ for widget in [w for w in (self.log, self.prompt) if w.tainted]:
+ widget.draw()
class _ConnectionWindow(_Window, BroadcastConnMixin):
class _ConnectionWindow(_Window, BroadcastConnMixin):
@@
-391,10
+420,8
@@
class _TuiLoop(Loop, BroadcastMixin):
self._term.calc_geometry()
for window in self._windows:
window.set_geometry()
self._term.calc_geometry()
for window in self._windows:
window.set_geometry()
- self.window.draw()
elif isinstance(event, _LogEvent):
self.window.log.append(event.payload)
elif isinstance(event, _LogEvent):
self.window.log.append(event.payload)
- self.window.log.draw()
elif isinstance(event, _TuiCmdEvent):
cmd = self._cmd_name_to_cmd(event.payload[0])
assert cmd is not None
elif isinstance(event, _TuiCmdEvent):
cmd = self._cmd_name_to_cmd(event.payload[0])
assert cmd is not None
@@
-418,13
+445,9
@@
class _TuiLoop(Loop, BroadcastMixin):
nick=event.payload)
elif isinstance(event, DisconnectedEvent):
conn_win.prompt.update_prompt(nick_confirmed=False)
nick=event.payload)
elif isinstance(event, DisconnectedEvent):
conn_win.prompt.update_prompt(nick_confirmed=False)
- if conn_win == self.window:
- if isinstance(event, LogConnEvent):
- self.window.log.draw()
- if isinstance(event, (NickSetEvent, DisconnectedEvent)):
- self.window.prompt.draw()
else:
return True
else:
return True
+ self.window.draw_tainted()
self._term.flush()
return True
self._term.flush()
return True