From 7185651b8cc9b5f3a1ff61b9316fbfb1941b162d Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 11 Mar 2026 08:07:26 +0100 Subject: [PATCH] Add thread_select_unread setting to open threads at oldest unread message When enabled, opening a thread selects the oldest unread matching message instead of the oldest matching message. Falls back to the most recent message if all messages are read. Also fixes a crash in get_last_msg_idx when called with an invalid root index. Co-Authored-By: Claude Opus 4.6 --- dodo/settings.py | 8 ++++++++ dodo/thread.py | 25 ++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/dodo/settings.py b/dodo/settings.py index c112eb2..44ddb39 100644 --- a/dodo/settings.py +++ b/dodo/settings.py @@ -151,6 +151,14 @@ * 'thread': tree view, following the various subthreads """ +thread_select_unread = False +"""When opening a thread, select the oldest unread matching message + +If True, the oldest unread matching message is selected when opening a thread. +If all messages are read, the most recent message is selected instead. +If False (the default), the oldest matching message is selected. +""" + gnupg_home = None """Directory containg GnuPG keys diff --git a/dodo/thread.py b/dodo/thread.py index ab9c50a..85f174d 100644 --- a/dodo/thread.py +++ b/dodo/thread.py @@ -307,6 +307,10 @@ def _fetch_matching_ids(self) -> set[str]: return set(json.loads(r.stdout)) def get_last_msg_idx(self, parent: QModelIndex=QModelIndex()) -> QModelIndex: + if not parent.isValid(): + if self.rowCount() == 0: + return QModelIndex() + return self.get_last_msg_idx(self.index(self.rowCount()-1, 0)) children = parent.internalPointer().children if children: return self.get_last_msg_idx(self.index(len(children)-1, 0, parent)) @@ -421,11 +425,22 @@ def find(self, msg_id: str) -> QModelIndex: def default_message(self) -> QModelIndex: """Return the index of either the oldest matching message or the last message - in the thread.""" - for idx in self.iterate_indices(): - if self.message_at(idx)['id'] in self.matches: - return idx - return self.get_last_msg_idx() + in the thread. + + If :func:`~dodo.settings.thread_select_unread` is True, prefer the oldest + unread matching message, falling back to the last message if all are read. + """ + if settings.thread_select_unread: + for idx in self.iterate_indices(): + msg = self.message_at(idx) + if msg['id'] in self.matches and 'unread' in msg['tags']: + return idx + return self.get_last_msg_idx() + else: + for idx in self.iterate_indices(): + if self.message_at(idx)['id'] in self.matches: + return idx + return self.get_last_msg_idx() def default_collapsed(self) -> set[str]: irrelevant_branches = set()