From: Christian Heller Date: Sat, 29 Nov 2025 22:11:16 +0000 (+0100) Subject: Preserve bookmark position over SIGWINCH. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/condition_titles?a=commitdiff_plain;h=HEAD;p=ircplom Preserve bookmark position over SIGWINCH. --- diff --git a/src/ircplom/tui_base.py b/src/ircplom/tui_base.py index 5def0f2..fdb02a3 100644 --- a/src/ircplom/tui_base.py +++ b/src/ircplom/tui_base.py @@ -50,6 +50,17 @@ _KEYBINDINGS = { } CMD_SHORTCUTS: dict[str, str] = {} +_UNSET_IDX_NEG = 0 +_UNSET_IDX_POS = -1 + + +class _YX(NamedTuple): + y: int + x: int + + +_SIZE_UNSET = _YX(_UNSET_IDX_POS, _UNSET_IDX_POS) + class FormattingString: 'For inserting terminal formatting directives, and escaping their syntax.' @@ -180,14 +191,6 @@ class FormattingString: return tuple(self.__class__(text, raw=True) for text in wrapped_lines) -class _YX(NamedTuple): - y: int - x: int - - -_SIZE_UNSET = _YX(-1, -1) - - class _Widget(ABC): _size = _SIZE_UNSET @@ -296,10 +299,10 @@ class _HistoryWidget(_ScrollableWidget): self._maxlen_log = maxlen_log self._len_to_term = len_to_term self._wrapped: list[_WrappedHistoryLine] = [] - self._lowest_read_history_idx_pos = self._UNSET_IDX_POS - self._bookmark_idx_neg = self._UNSET_IDX_NEG + self._lowest_read_history_idx_pos = _UNSET_IDX_POS + self._bookmark_idx_neg = _UNSET_IDX_NEG self._history_n_lines_cut = 0 - self._history_idx_neg = self._UNSET_IDX_NEG + self._history_idx_neg = _UNSET_IDX_NEG def _add_wrapped_from_offset_history(self, history_idx_pos: int, @@ -318,6 +321,13 @@ class _HistoryWidget(_ScrollableWidget): def set_geometry(self, size: _YX) -> None: super().set_geometry(size) + keep_bookmark_at = ( + _UNSET_IDX_POS + if (self._bookmark_idx_neg == _UNSET_IDX_NEG + or len(self._wrapped) - 1 < -self._bookmark_idx_neg) + else (self._wrapped[self._bookmark_idx_neg - 1].history_idx_pos + if self._bookmark_idx_neg != self._BOTTOM_IDX_NEG + else (self._len_full_history - 1))) self._unbookmark() if self._drawable: self._y_pgscroll = self._size.y // 2 @@ -329,19 +339,19 @@ class _HistoryWidget(_ScrollableWidget): # ensure that of the full line identified by ._history_idx_neg, # ._wrapped_idx_neg point to the lowest of its wrap parts self._wrapped_idx_neg = ( - self._UNSET_IDX_NEG if (not self._wrapped) + _UNSET_IDX_NEG if not self._wrapped else (self._wrapped_top_idx_neg + self._bottom_wrapped_for_history_idx_pos( self._len_full_history + max(self._history_idx_neg, - self._maxlen_log)))) - self.bookmark() + self.bookmark(keep_bookmark_at) def append_fmt(self, to_append: FormattingString) -> None: 'Wrap .append around FormattingString, update history dependents.' def start_unset_dec_non_bottom(attr_name: str, grow_by: int) -> None: full_name = f'_{attr_name}_idx_neg' cur_val = getattr(self, full_name) - if cur_val == self._UNSET_IDX_NEG: + if cur_val == _UNSET_IDX_NEG: setattr(self, full_name, self._BOTTOM_IDX_NEG) elif cur_val < self._BOTTOM_IDX_NEG: setattr(self, full_name, cur_val - grow_by) @@ -354,7 +364,7 @@ class _HistoryWidget(_ScrollableWidget): = self._add_wrapped_from_offset_history(len(self._history) - 1, to_append) start_unset_dec_non_bottom('wrapped', grow_by=wrapped_growth) - if self._bookmark_idx_neg != self._UNSET_IDX_NEG: + if self._bookmark_idx_neg != _UNSET_IDX_NEG: self._bookmark_idx_neg -= wrapped_growth if len(self._history) > self._maxlen_log: self._history.pop(0) @@ -387,17 +397,19 @@ class _HistoryWidget(_ScrollableWidget): visible_lines[-1].history_idx_pos) def _unbookmark(self) -> None: - if self._bookmark_idx_neg != self._UNSET_IDX_NEG\ + if self._bookmark_idx_neg != _UNSET_IDX_NEG\ and len(self._wrapped) > -self._bookmark_idx_neg: del self._wrapped[self._bookmark_idx_neg] if self._bookmark_idx_neg > self._wrapped_idx_neg: self._wrapped_idx_neg += 1 - self._bookmark_idx_neg = self._UNSET_IDX_NEG + self._bookmark_idx_neg = _UNSET_IDX_NEG - def bookmark(self) -> None: + def bookmark(self, preceding_history_idx_pos=_UNSET_IDX_POS) -> None: 'Store next idx to what most recent line we have (been) scrolled.' self._unbookmark() - if self._lowest_read_history_idx_pos < self._history_n_lines_cut: + if preceding_history_idx_pos == _UNSET_IDX_POS: + preceding_history_idx_pos = self._lowest_read_history_idx_pos + if preceding_history_idx_pos < self._history_n_lines_cut: return if not self._wrapped: return @@ -406,7 +418,7 @@ class _HistoryWidget(_ScrollableWidget): lowest_read_wrapped_idx_neg\ = (self._wrapped_top_idx_neg + self._bottom_wrapped_for_history_idx_pos( - self._lowest_read_history_idx_pos)) + preceding_history_idx_pos)) if lowest_read_wrapped_idx_neg == self._BOTTOM_IDX_NEG: self._bookmark_idx_neg = self._BOTTOM_IDX_NEG self._wrapped += [bookmark] diff --git a/src/tests/lib/servermsglogged b/src/tests/lib/servermsglogged index b2f21da..1899b7a 100644 --- a/src/tests/lib/servermsglogged +++ b/src/tests/lib/servermsglogged @@ -1,3 +1,6 @@ +× servermsglogged-core +servermsg SERVER_ID MSG +log WIN_ID < MSG + × servermsglogged -servermsg 0 MSG -log 1 < MSG +insert servermsglogged-core : + SERVER_ID=0 WIN_ID :1 diff --git a/src/tests/test.test b/src/tests/test.test index 88ad5cc..51b90c1 100644 --- a/src/tests/test.test +++ b/src/tests/test.test @@ -19,11 +19,12 @@ insert ./lib/privmsg insert ./lib/req-sasl insert ./lib/retry-in insert ./lib/servermsglogged +# for: servermsglogged-core, servermsglogged insert ./lib/servernotice insert ./lib/user-set-to × nick-increment -insert servermsglogged : +0 MSG ::*.?.net 433 * NAME_A :Nickname already in use +insert servermsglogged : + MSG ::*.?.net 433 * NAME_A :Nickname already in use log 1 $ nickname already in use, trying increment log 1 > NICK :NAME_B @@ -32,23 +33,23 @@ log 1 > NICK :NAME_B × conn_init_1 # expect some NOTICE and PING to process/reply during initiation -insert servernotice : +0 XXX :*** Looking up your ident... -insert servernotice : +0 XXX :*** Looking up your hostname... -insert servernotice : +0 XXX :*** Found your hostname (baz.bar.foo) -insert ping-pong : +0 +insert servernotice : + XXX :*** Looking up your ident... +insert servernotice : + XXX :*** Looking up your hostname... +insert servernotice : + XXX :*** Found your hostname (baz.bar.foo) +insert ping-pong # handle 433 -insert nick-increment : +0 NAME_A=foo NAME_B :foo0 -insert nick-increment : +0 NAME_A=foo0 NAME_B :foo1 +insert nick-increment : + NAME_A=foo NAME_B :foo0 +insert nick-increment : + NAME_A=foo0 NAME_B :foo1 # collect server capabilities -insert cap-msg : +0 CAPMSG :* LS : foo bar sasl=IGNORE baz cap-notify +insert cap-msg : + CAPMSG :* LS : foo bar sasl=IGNORE baz cap-notify log 1 $ caps:bar:data set to: [] log 1 $ caps:baz:data set to: [] log 1 $ caps:cap-notify:data set to: [] log 1 $ caps:foo:data set to: [] log 1 $ caps:sasl:data set to: [IGNORE] -insert req-sasl : +0 foo=foo1 REPLY=ACK CAPLIST :cap-notify sasl +insert req-sasl : + foo=foo1 REPLY=ACK CAPLIST :cap-notify sasl log 1 $ caps:cap-notify:enabled set to: [True] log 1 $ caps:sasl:enabled set to: [True] @@ -60,38 +61,38 @@ log 1 > CAP :END × conn_init_2 # of all pre-MOTD greeting messages, only process isupports -insert servermsglogged : +0 MSG ::foo.bar.baz 001 foo1 :Welcome to the foo.bar.baz network -insert servermsglogged : +0 MSG ::foo.bar.baz 002 foo1 :Your host is foo.bar.baz -insert servermsglogged : +0 MSG ::foo.bar.baz 003 foo1 :This server was created Jan 1 2020 -insert servermsglogged : +0 MSG ::foo.bar.baz 004 foo1 foo.bar.baz ircserver-1.0 abc def ghi -insert servermsglogged : +0 MSG ::foo.bar.baz 005 foo1 ABC=DEF GHI :are supported by this server +insert servermsglogged : + MSG ::foo.bar.baz 001 foo1 :Welcome to the foo.bar.baz network +insert servermsglogged : + MSG ::foo.bar.baz 002 foo1 :Your host is foo.bar.baz +insert servermsglogged : + MSG ::foo.bar.baz 003 foo1 :This server was created Jan 1 2020 +insert servermsglogged : + MSG ::foo.bar.baz 004 foo1 foo.bar.baz ircserver-1.0 abc def ghi +insert servermsglogged : + MSG ::foo.bar.baz 005 foo1 ABC=DEF GHI :are supported by this server log 1 $ isupport:ABC set to: [DEF] log 1 $ isupport:GHI set to: [] -insert servermsglogged : +0 MSG ::foo.bar.baz 251 foo1 :There are 10 users and 1000 invisible on 5 servers -insert servermsglogged : +0 MSG ::foo.bar.baz 252 foo1 7 :IRC Operators online -insert servermsglogged : +0 MSG ::foo.bar.baz 253 foo1 4 :unknown connection(s) -insert servermsglogged : +0 MSG ::foo.bar.baz 254 foo1 800 :channels formed -insert servermsglogged : +0 MSG ::foo.bar.baz 255 foo1 :I have 100 clients and 1 serveres -insert servermsglogged : +0 MSG ::foo.bar.baz 265 foo1 100 150 :Current local users 100, max 150 -insert servermsglogged : +0 MSG ::foo.bar.baz 266 foo1 1010 1050 :Current global users 1010, max 1050 -insert servermsglogged : +0 MSG ::foo.bar.baz 250 foo1 :Highest connection count: 151 (150 clients) (1080 connections received) +insert servermsglogged : + MSG ::foo.bar.baz 251 foo1 :There are 10 users and 1000 invisible on 5 servers +insert servermsglogged : + MSG ::foo.bar.baz 252 foo1 7 :IRC Operators online +insert servermsglogged : + MSG ::foo.bar.baz 253 foo1 4 :unknown connection(s) +insert servermsglogged : + MSG ::foo.bar.baz 254 foo1 800 :channels formed +insert servermsglogged : + MSG ::foo.bar.baz 255 foo1 :I have 100 clients and 1 serveres +insert servermsglogged : + MSG ::foo.bar.baz 265 foo1 100 150 :Current local users 100, max 150 +insert servermsglogged : + MSG ::foo.bar.baz 266 foo1 1010 1050 :Current global users 1010, max 1050 +insert servermsglogged : + MSG ::foo.bar.baz 250 foo1 :Highest connection count: 151 (150 clients) (1080 connections received) # collect MOTD into a single output (rather than line-by-line) -insert servermsglogged : +0 MSG ::foo.bar.baz 375 foo1 :- foo.bar.baz Message of the Day - -insert servermsglogged : +0 MSG ::foo.bar.baz 372 foo1 :- Howdy! - -insert servermsglogged : +0 MSG ::foo.bar.baz 372 foo1 :- Welcome! - -insert servermsglogged : +0 MSG ::foo.bar.baz 372 foo1 :- (to this server) - -insert servermsglogged : +0 MSG ::foo.bar.baz 376 foo1 :End of /MOTD command +insert servermsglogged : + MSG ::foo.bar.baz 375 foo1 :- foo.bar.baz Message of the Day - +insert servermsglogged : + MSG ::foo.bar.baz 372 foo1 :- Howdy! - +insert servermsglogged : + MSG ::foo.bar.baz 372 foo1 :- Welcome! - +insert servermsglogged : + MSG ::foo.bar.baz 372 foo1 :- (to this server) - +insert servermsglogged : + MSG ::foo.bar.baz 376 foo1 :End of /MOTD command log 1 $ motd set to: [- Howdy! -], [- Welcome! -], [- (to this server) -] log 2 $ - Howdy! - log 2 $ - Welcome! - log 2 $ - (to this server) - # collect user mode -insert servermsglogged : +0 MSG ::foo1 MODE foo1 :+Ziw +insert servermsglogged : + MSG ::foo1 MODE foo1 :+Ziw log 1 $ users:me:modes set to: [Ziw] # handle bot query NOTICE -insert servermsglogged : +0 MSG ::SaslServ!SaslServ@services.bar.baz NOTICE foo1 :Last login from ~baz@foo.bar.baz on Jan 1 22:00:00 2021 +0000. +insert servermsglogged : + MSG ::SaslServ!SaslServ@services.bar.baz NOTICE foo1 :Last login from ~baz@foo.bar.baz on Jan 1 22:00:00 2021 +0000. log 3 < (SaslServ) Last login from ~baz@foo.bar.baz on Jan 1 22:00:00 2021 +0000. @@ -100,53 +101,53 @@ log 3 < (SaslServ) Last login from ~baz@foo.bar.baz on Jan 1 22:00:00 2021 +0000 # test recoverable 432 insert cmd-nick :2 +1 NEWNICK :@foo -insert servermsglogged : +0 MSG ::*.?.net 432 foo1 @foo :Erroneous nickname +insert servermsglogged : + MSG ::*.?.net 432 foo1 @foo :Erroneous nickname log 1 $ nickname refused for bad format, keeping current one # rename to easen code-reuse -insert cmd-nick :4 +0 NUH=foo1!~baz@baz.bar.foo NEWNICK :foo +insert cmd-nick :4 + NUH=foo1!~baz@baz.bar.foo NEWNICK :foo log 1 $ users:me:user set to: [~baz] log 1 $ users:me:host set to: [baz.bar.foo] insert cmd-nick 4:-2 +1 USER_ID=me NEWNICK :foo log rename_win_ids $ foo1!~baz@baz.bar.foo renames foo # join channel with other user -insert join-channel-0 : +0 CHANNEL=#test RESIDENT_NAMES :foo @baz +insert join-channel-0 : + CHANNEL=#test RESIDENT_NAMES :foo @baz insert user-set-to :1 +1 USER_ID=1 USERNICK :baz log 1 $ channels:#test:prefixes:@ set to: [1] -insert join-channel-1 : +0 CHANNEL=#test RESIDENT_IDS :[1], [me] +insert join-channel-1 : + CHANNEL=#test RESIDENT_IDS :[1], [me] log 4 $ residents: @baz, foo # process non-self channel JOIN -insert servermsglogged : +0 MSG ::bar!~bar@bar.bar JOIN :#test +insert servermsglogged : + MSG ::bar!~bar@bar.bar JOIN :#test insert user-set-to : +1 USER_ID=2 USERNICK=bar USERNAME=~bar USERHOST :bar.bar log 1 $ channels:#test:user_ids set to: [1], [2], [me] log 4 $ bar!~bar@bar.bar joins # join second channel with partial residents identity to compare distribution of resident-specific messages -insert join-channel-0 : +0 CHANNEL=#testtest RESIDENT_NAMES :foo bar -insert join-channel-1 : +0 CHANNEL=#testtest RESIDENT_IDS :[2], [me] +insert join-channel-0 : + CHANNEL=#testtest RESIDENT_NAMES :foo bar +insert join-channel-1 : + CHANNEL=#testtest RESIDENT_IDS :[2], [me] log 5 $ residents: bar, foo # handle query window with known user -insert servermsglogged : +0 MSG ::bar!~bar@bar.bar PRIVMSG foo :hi there +insert servermsglogged : + MSG ::bar!~bar@bar.bar PRIVMSG foo :hi there log 6 < [bar] hi there insert privmsg : +1 TARGET_WIN_ID=6 TARGET=bar TXT :hello, how is it going -insert servermsglogged : +0 MSG ::bar!~bar@bar.bar PRIVMSG foo :fine! +insert servermsglogged : + MSG ::bar!~bar@bar.bar PRIVMSG foo :fine! log 6 < [bar] fine! # handle failure to query absent user insert privmsg : +1 TARGET_WIN_ID=7 TARGET=barbar TXT :hello! -insert servermsglogged : +0 MSG ::*.?.net 401 foo barbar :No such nick/channel +insert servermsglogged : + MSG ::*.?.net 401 foo barbar :No such nick/channel log 7 $ barbar not online # handle non-self renaming, even into window of previously not found target -insert servermsglogged : +0 MSG ::bar!~bar@bar.bar NICK :barbar +insert servermsglogged : + MSG ::bar!~bar@bar.bar NICK :barbar log 1 $ users:2:nick set to: [barbar] log 4,5,6,7 $ bar!~bar@bar.bar renames barbar # handle non-self QUIT -insert servermsglogged : +0 MSG ::barbar!~bar@bar.bar QUIT :Client Quit +insert servermsglogged : + MSG ::barbar!~bar@bar.bar QUIT :Client Quit log 1 $ users:2:exit_msg set to: [QClient Quit] log 6,7 $ barbar!~bar@bar.bar quits: Client Quit insert quit : + CHAN_WIN_ID=4 CHANNEL=#test USER_ID=2 NICK=bar bar!=barbar! REMAINING_IDS :[1], [me] @@ -159,13 +160,13 @@ insert part : + CHAN_WIN_ID=4 CHANNEL=#test USERIDS_CLEAR :set to: [1] log 1 $ users:1 deleted # handle lack of implementation -insert no-handler : +0 ALERT_WIN_IDS=2,3,4,5,6,7 ? :foo bar baz +insert no-handler : + ALERT_WIN_IDS=2,3,4,5,6,7 ? :foo bar baz # handle /disconnect, clear all insert cmd-disconnect-0 :-1 log 3,6,7 $ foo!~baz@baz.bar.foo quits: Client Quit insert quits : + CHAN_WIN_ID=5 CHANNEL=#testtest USER_ID=me NICK=foo foo@foo.foo=baz@baz.bar.foo USERIDS_CLEAR :emptied -insert cmd-disconnect-1 : +0 +insert cmd-disconnect-1 insert disconnect1 :-1 +1 WIN_IDS :2,3,4,5,6,7 log 1 $ motd emptied log 1 $ users cleared @@ -227,7 +228,7 @@ log 1 # /window.prompt.scroll DIRECTION log 1 # /window.raw VERB [PARAMS_STR] log 1 # /window.reconnect -insert during_conn : +0 rename_win_ids :3 +insert during_conn : + rename_win_ids :3 # test setting up second client, but 432 irrecoverably > /connect baz.bar.foo:6697 ?foo bar:baz @@ -236,8 +237,7 @@ insert attempting :2 +8 foo.bar.baz :baz.bar.foo log 9 $ - nickname: ?foo insert attempting-to-connected 3:-1 +8 WIN_IDS=2 foo.bar.baz :baz.bar.foo log 8 > NICK :?foo -servermsg 1 :*.?.net 432 * ?foo :Erroneous nickname -log 8 < :*.?.net 432 * ?foo :Erroneous nickname +insert servermsglogged-core : + SERVER_ID=1 WIN_ID=8 MSG ::*.?.net 432 * ?foo :Erroneous nickname insert isupport-clear : +8 insert disconnect1 1:-1 +8 WIN_IDS :2 log 8 $ nickname refused for bad format, giving up @@ -262,4 +262,4 @@ insert conn_init_1 insert conn_init_2 :2 log 1 > JOIN :#testtest insert conn_init_2 2: -insert during_conn : +0 rename_win_ids :3,6,7 +insert during_conn : + rename_win_ids :3,6,7 diff --git a/src/tests/tui_draw.test b/src/tests/tui_draw.test index a55c322..3daece1 100644 --- a/src/tests/tui_draw.test +++ b/src/tests/tui_draw.test @@ -379,6 +379,13 @@ insert line-bookmark : +19 insert history_0 26:28 +20 insert lines-status-prompt-start : +22 X123456789X123456789X :=========([0] 1 (2:7) +# check SIGWINCH does not re-set bookmark +> raise_sigwinch +insert history_0 7:26 +0 +insert line-bookmark : +19 +insert history_0 26:28 +20 +insert lines-status-prompt-start : +22 X123456789X123456789X :=========([0] 1 (2:7) + # check that scrolling non-bottom bookmark out of sight, then scrolling it back into view again does not by itself move its position in the log history > /window.history.scroll up insert lines-empty :3