[PATCH WIP] emacs: show: redesign unread/read logic
authorMark Walters <markwalters1009@gmail.com>
Sun, 24 Nov 2013 09:32:31 +0000 (09:32 +0000)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:58:27 +0000 (09:58 -0800)
e2/a4a0f86b9d433e8452b44025c48f9ed2c7e842 [new file with mode: 0644]

diff --git a/e2/a4a0f86b9d433e8452b44025c48f9ed2c7e842 b/e2/a4a0f86b9d433e8452b44025c48f9ed2c7e842
new file mode 100644 (file)
index 0000000..123f29e
--- /dev/null
@@ -0,0 +1,420 @@
+Return-Path: <markwalters1009@gmail.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 5249C431FCB\r
+       for <notmuch@notmuchmail.org>; Sun, 24 Nov 2013 01:39:55 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0.224\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0.224 tagged_above=-999 required=5\r
+       tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+       FREEMAIL_ENVFROM_END_DIGIT=1, FREEMAIL_FROM=0.001,\r
+       HS_INDEX_PARAM=0.023, RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id HGMUTnX4A97j for <notmuch@notmuchmail.org>;\r
+       Sun, 24 Nov 2013 01:39:50 -0800 (PST)\r
+Received: from mail-wg0-f53.google.com (mail-wg0-f53.google.com\r
+ [74.125.82.53])       (using TLSv1 with cipher RC4-SHA (128/128 bits))        (No client\r
+ certificate requested)        by olra.theworths.org (Postfix) with ESMTPS id\r
+ 53053431FC3   for <notmuch@notmuchmail.org>; Sun, 24 Nov 2013 01:39:50 -0800\r
+ (PST)\r
+Received: by mail-wg0-f53.google.com with SMTP id k14so149659wgh.32\r
+       for <notmuch@notmuchmail.org>; Sun, 24 Nov 2013 01:39:47 -0800 (PST)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+       h=from:to:cc:subject:date:message-id;\r
+       bh=w20NEW5TIVkDcbu8+azbo1J7UzQ6FwFMS1ubObuq6ok=;\r
+       b=HP8nsDAabampXERYUwCVOF+cZjNAUz11zJGN6yUvvgaTPeAj/i/pOVjxKihaidHqJx\r
+       OBOZEfPlR/+3Y4sLMrvRkjD0RUujz2gAOvvQft0nn/JQGyJUnv2V1ma8+QiSICFk5b6N\r
+       PFssIr2j1dXgeD55apk+PBSFlUoBLc8WBh2dJpyQcLJaD5ZzBNuh4XS/n7b3Kj25Mkm1\r
+       yAlYeKi9iqF1XfJ2eTNjz0O8L0URBxLx7mdq7eyu18ZYt2VykGAvBkFOKaOrpNFEBAV/\r
+       j7qCzI0LgJ8uyVVXVnhNgyvgCyuIFjbIgz0b9uhLbjXSr0LhQMg0A5UN/9snlg7OntZN\r
+       tkvw==\r
+X-Received: by 10.194.89.105 with SMTP id bn9mr68494wjb.82.1385285563609;\r
+       Sun, 24 Nov 2013 01:32:43 -0800 (PST)\r
+Received: from localhost (93-97-24-31.zone5.bethere.co.uk. [93.97.24.31])\r
+       by mx.google.com with ESMTPSA id gb1sm34255955wic.0.2013.11.24.01.32.42\r
+       for <multiple recipients>\r
+       (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
+       Sun, 24 Nov 2013 01:32:43 -0800 (PST)\r
+From: Mark Walters <markwalters1009@gmail.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH WIP] emacs: show: redesign unread/read logic\r
+Date: Sun, 24 Nov 2013 09:32:31 +0000\r
+Message-Id: <1385285551-5158-1-git-send-email-markwalters1009@gmail.com>\r
+X-Mailer: git-send-email 1.7.9.1\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sun, 24 Nov 2013 09:39:55 -0000\r
+\r
+The decisions of when to mark messages read in notmuch-show has caused\r
+confusion/irritation (several discussions on irc and the mailing list\r
+eg the thread starting at id:87hadi0xse.fsf@boo.workgroup). This is an\r
+attempt to get some logic that people are happier with.\r
+\r
+Some examples of the current problems are: notmuch marks sometimes\r
+closed messages read, notmuch does not mark messages read if you page\r
+down through them, and notmuch removes the unread tag too soon: when\r
+you first see the message you do not if you have read it before.\r
+\r
+The patch separates out two things "seeing" a message and "marking it\r
+read".\r
+\r
+A message is deemed seen if both the top and bottom of the message\r
+have both been visible in the buffer's window. This is chosen so that\r
+just seeing 1 or 2 lines of a message at the bottom of the window does\r
+not mark it seen. A closed message is never marked seen.\r
+\r
+The seen status is updated via a command-hook (run on every\r
+command/key-press) so essentially any change which sees a message\r
+should mark it as seen.\r
+\r
+By default the unread status of seen messages is not updated until the\r
+user quits the show buffer, and the user has the option of prefix-arg\r
+quit to exit the show buffer without updating the unread status.\r
+\r
+However, if the user sets the custom variable\r
+notmuch-show-update-unread-on-seen then the unread status is updated\r
+(ie unread tag is removed) as soon as a message is seen (in the above\r
+sense).\r
+---\r
+\r
+This patch brings the unread handling roughly in line with what I\r
+would expect, and is reasonably close to the suggestions from Austin\r
+id:20131005162202.GJ21611@mit.edu and Jani\r
+id:87vc1aho64.fsf@nikula.org. It was also clear from the discussion\r
+that different people want different things so we will need some\r
+customisation possibilities.\r
+\r
+It is a large patch: I am afraid I don't see a way round that.\r
+\r
+At the moment there are two things that need fixing: first tree-view\r
+assumes the old behaviour so its displayed tags get out of sync with\r
+the actual tags. Secondly, a lot of tests fail for the obvious reason\r
+that the unread tag is not removed at the same time as before.\r
+\r
+It would be very helpful if people could test and see whether it works\r
+as they would like, and if not say why/when it is doing the wrong\r
+thing and what it should do in those cases.\r
+\r
+Best wishes\r
+\r
+Mark\r
+\r
+\r
+ emacs/notmuch-show.el |  151 ++++++++++++++++++++++++++++++++++++++++++++-----\r
+ emacs/notmuch-tree.el |   20 +++++--\r
+ 2 files changed, 151 insertions(+), 20 deletions(-)\r
+\r
+diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
+index 784644c..1081eb0 100644\r
+--- a/emacs/notmuch-show.el\r
++++ b/emacs/notmuch-show.el\r
+@@ -168,6 +168,10 @@ each attachment handler is logged in buffers with names beginning\r
+ \" *notmuch-part*\". This option requires emacs version at least\r
+ 24.3 to work.")\r
\r
++(defvar notmuch-show-seen-plist nil)\r
++(make-variable-buffer-local 'notmuch-show-seen-plist)\r
++(put 'notmuch-show-seen-plist 'permanent-local t)\r
++\r
+ (defcustom notmuch-show-stash-mlarchive-link-alist\r
+   '(("Gmane" . "http://mid.gmane.org/")\r
+     ("MARC" . "http://marc.info/?i=")\r
+@@ -211,6 +215,15 @@ For example, if you wanted to remove an \"unread\" tag and add a\r
+   :type '(repeat string)\r
+   :group 'notmuch-show)\r
\r
++(defcustom notmuch-show-update-unread-on-seen nil\r
++  "Update unread tags when seen rathe than when exiting show buffer.\r
++\r
++A message is seen if the top and bottom of the message have both\r
++been visible in the buffer. When this is nil the unread status is\r
++updated on exiting the show buffer. When this is t the unread\r
++status is updated as soon as the message is seen."\r
++  :type 'boolean\r
++  :group 'notmuch-show)\r
\r
+ (defmacro with-current-notmuch-show-message (&rest body)\r
+   "Evaluate body with current buffer set to the text of current message"\r
+@@ -1142,6 +1155,8 @@ function is used."\r
+   (let ((inhibit-read-only t))\r
\r
+     (notmuch-show-mode)\r
++    (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)\r
++\r
+     ;; Don't track undo information for this buffer\r
+     (set 'buffer-undo-list t)\r
\r
+@@ -1213,6 +1228,12 @@ preferences. If invoked with a prefix argument (or RESET-STATE is\r
+ non-nil) then the state of the buffer (open/closed messages) is\r
+ reset based on the original query."\r
+   (interactive "P")\r
++  ;; Do not mark seen messages read if we are resetting state. The\r
++  ;; idea is that resetting state is asking for the view to be reset\r
++  ;; to the current state of the database.\r
++  (unless notmuch-show-update-unread-on-seen\r
++    (notmuch-show-mark-all-seen-read reset-state))\r
++\r
+   (let ((inhibit-read-only t)\r
+       (state (unless reset-state\r
+                (notmuch-show-capture-state))))\r
+@@ -1258,6 +1279,8 @@ reset based on the original query."\r
+ (defvar notmuch-show-mode-map\r
+       (let ((map (make-sparse-keymap)))\r
+       (set-keymap-parent map notmuch-common-keymap)\r
++      ;; the following overrides the common-keymap quit\r
++      (define-key map [remap notmuch-kill-this-buffer] 'notmuch-show-quit-and-mark-read)\r
+       (define-key map "Z" 'notmuch-tree-from-show-current-query)\r
+       (define-key map (kbd "<C-tab>") 'widget-backward)\r
+       (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)\r
+@@ -1525,6 +1548,114 @@ marked as unread, i.e. the tag changes in\r
+     (apply 'notmuch-show-tag-message\r
+          (notmuch-tag-change-list notmuch-show-mark-read-tags unread))))\r
\r
++(defun notmuch-show-is-unread ()\r
++  "Return t if current message is unread.\r
++\r
++Returns t unless applying `notmuch-show-mark-read-tags' would be\r
++a no-op"\r
++  (when notmuch-show-mark-read-tags\r
++    (let* ((current-tags (notmuch-show-get-tags))\r
++         (tag-changes (notmuch-tag-change-list notmuch-show-mark-read-tags))\r
++         (new-tags (notmuch-update-tags current-tags tag-changes)))\r
++      (not (equal current-tags new-tags)))))\r
++\r
++(defun notmuch-show-message-seen ()\r
++  "Return t if top and bottom of current message have been seen."\r
++  (eq (lax-plist-get notmuch-show-seen-plist\r
++                   (notmuch-show-get-message-id))\r
++      'both))\r
++\r
++(defun notmuch-show-mark-all-seen-read (&optional not-mark)\r
++  "Mark read all messages that have been seen in this buffer.\r
++\r
++If NOT-MARK then do not mark the messages read, and tell the user\r
++we are not marking them."\r
++  (if not-mark\r
++      (message "Not marking messages read")\r
++    (let ((messages-to-mark-read))\r
++      ;; We get a list of all message to tag read. A list means that\r
++      ;; we can tag all the messages in one tag operation rather than\r
++      ;; needing one per read message.\r
++      (notmuch-show-mapc\r
++       (lambda ()\r
++       (when (and (notmuch-show-message-seen) (notmuch-show-is-unread))\r
++         (push (notmuch-show-get-message-id) messages-to-mark-read))))\r
++      (when messages-to-mark-read\r
++      (notmuch-tag (mapconcat #'identity messages-to-mark-read " ")\r
++                   (notmuch-tag-change-list notmuch-show-mark-read-tags)))\r
++      (let ((count (length messages-to-mark-read)))\r
++      (cond ((> count 1)\r
++             (message "Marked %s messages read" count))\r
++            ((= count 1)\r
++             (message "Marked one message read"))\r
++            ((= count 0)\r
++             (message "No messages marked read")))))))\r
++\r
++(put 'notmuch-show-quit-and-mark-read 'notmuch-prefix-doc\r
++     "... without marking seen messages read.")\r
++(defun notmuch-show-quit-and-mark-read (&optional not-mark)\r
++  "Kill the current buffer marking seen messages read."\r
++  (interactive "P")\r
++  (unless notmuch-show-update-unread-on-seen\r
++    (notmuch-show-mark-all-seen-read not-mark))\r
++  (notmuch-kill-this-buffer))\r
++\r
++(defun notmuch-show-update-seen (top-or-bottom)\r
++  "Update seen status of current message\r
++\r
++Mark that we have seen the TOP-OR-BOTTOM of current message."\r
++  (let* ((id (notmuch-show-get-message-id))\r
++       (current (lax-plist-get notmuch-show-seen-plist id))\r
++       new)\r
++    (unless (eq current 'both)\r
++      (if (eq top-or-bottom 'top)\r
++        (if (eq current 'bottom)\r
++            (setq new 'both)\r
++          (setq new 'top))\r
++      (if (eq current 'top)\r
++          (setq new 'both)\r
++        (setq new 'bottom)))\r
++      (unless (eq current new)\r
++      (setq notmuch-show-seen-plist (lax-plist-put notmuch-show-seen-plist id new)))\r
++      (when (and notmuch-show-update-unread-on-seen\r
++               (eq new 'both))\r
++      (notmuch-show-mark-read)))))\r
++\r
++(defun notmuch-show-mark-message-seen (start end)\r
++  "Mark top and bottom of current message seen if between START and END."\r
++  (when (notmuch-show-message-visible-p)\r
++    (when (>= (notmuch-show-message-top) start)\r
++      (notmuch-show-update-seen 'top))\r
++    (when (<= (notmuch-show-message-bottom) end)\r
++      (notmuch-show-update-seen 'bottom))))\r
++\r
++(defun notmuch-show-mark-seen (start end)\r
++  "Update seen status for all open messages between start and end.\r
++\r
++A message is seen if both the top and bottom of the message have\r
++been visible in the buffer. Seen is a buffer local property. By\r
++default the unread status is removed from all seen messages when\r
++the user quits the show buffer. However, if\r
++`notmuch-show-update-unread-on-seen' is set then the unread\r
++status is removed as soon as the message is seen."\r
++  (save-excursion\r
++    (goto-char start)\r
++    (notmuch-show-mark-message-seen start end)\r
++    (while (and (< (notmuch-show-message-bottom) end)\r
++              (notmuch-show-goto-message-next))\r
++      (notmuch-show-mark-message-seen start end))\r
++    ;; This is a work around because emacs gives weird answers for\r
++    ;; window-end if the buffer ends with invisible text.\r
++    (when (and (pos-visible-in-window-p (point-max))\r
++             (notmuch-show-message-visible-p))\r
++      (notmuch-show-update-seen 'bottom))))\r
++\r
++(defun notmuch-show-command-hook ()\r
++  (when (eq major-mode 'notmuch-show-mode)\r
++    ;; We need to redisplay to get window-start and window-end correct.\r
++    (redisplay)\r
++    (notmuch-show-mark-seen (window-start) (window-end))))\r
++\r
+ ;; Functions for getting attributes of several messages in the current\r
+ ;; thread.\r
\r
+@@ -1660,9 +1791,7 @@ If a prefix argument is given and this is the last message in the\r
+ thread, navigate to the next thread in the parent search buffer."\r
+   (interactive "P")\r
+   (if (notmuch-show-goto-message-next)\r
+-      (progn\r
+-      (notmuch-show-mark-read)\r
+-      (notmuch-show-message-adjust))\r
++      (notmuch-show-message-adjust)\r
+     (if pop-at-end\r
+       (notmuch-show-next-thread)\r
+       (goto-char (point-max)))))\r
+@@ -1673,7 +1802,6 @@ thread, navigate to the next thread in the parent search buffer."\r
+   (if (= (point) (notmuch-show-message-top))\r
+       (notmuch-show-goto-message-previous)\r
+     (notmuch-show-move-to-message-top))\r
+-  (notmuch-show-mark-read)\r
+   (notmuch-show-message-adjust))\r
\r
+ (defun notmuch-show-next-open-message (&optional pop-at-end)\r
+@@ -1688,9 +1816,7 @@ to show, nil otherwise."\r
+     (while (and (setq r (notmuch-show-goto-message-next))\r
+               (not (notmuch-show-message-visible-p))))\r
+     (if r\r
+-      (progn\r
+-        (notmuch-show-mark-read)\r
+-        (notmuch-show-message-adjust))\r
++      (notmuch-show-message-adjust)\r
+       (if pop-at-end\r
+         (notmuch-show-next-thread)\r
+       (goto-char (point-max))))\r
+@@ -1703,9 +1829,7 @@ to show, nil otherwise."\r
+     (while (and (setq r (notmuch-show-goto-message-next))\r
+               (not (notmuch-show-get-prop :match))))\r
+     (if r\r
+-      (progn\r
+-        (notmuch-show-mark-read)\r
+-        (notmuch-show-message-adjust))\r
++      (notmuch-show-message-adjust)\r
+       (goto-char (point-max)))))\r
\r
+ (defun notmuch-show-open-if-matched ()\r
+@@ -1716,8 +1840,7 @@ to show, nil otherwise."\r
+ (defun notmuch-show-goto-first-wanted-message ()\r
+   "Move to the first open message and mark it read"\r
+   (goto-char (point-min))\r
+-  (if (notmuch-show-message-visible-p)\r
+-      (notmuch-show-mark-read)\r
++  (unless (notmuch-show-message-visible-p)\r
+     (notmuch-show-next-open-message))\r
+   (when (eobp)\r
+     ;; There are no matched non-excluded messages so open all matched\r
+@@ -1725,8 +1848,7 @@ to show, nil otherwise."\r
+     (notmuch-show-mapc 'notmuch-show-open-if-matched)\r
+     (force-window-update)\r
+     (goto-char (point-min))\r
+-    (if (notmuch-show-message-visible-p)\r
+-      (notmuch-show-mark-read)\r
++    (unless (notmuch-show-message-visible-p)\r
+       (notmuch-show-next-open-message))))\r
\r
+ (defun notmuch-show-previous-open-message ()\r
+@@ -1736,7 +1858,6 @@ to show, nil otherwise."\r
+                 (notmuch-show-goto-message-previous)\r
+               (notmuch-show-move-to-message-top))\r
+             (not (notmuch-show-message-visible-p))))\r
+-  (notmuch-show-mark-read)\r
+   (notmuch-show-message-adjust))\r
\r
+ (defun notmuch-show-view-raw-message ()\r
+diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el\r
+index 8d59e65..206bd9f 100644\r
+--- a/emacs/notmuch-tree.el\r
++++ b/emacs/notmuch-tree.el\r
+@@ -487,17 +487,20 @@ Shows in split pane or whole window according to value of\r
+   (when (notmuch-tree-scroll-message-window)\r
+     (notmuch-tree-next-matching-message)))\r
\r
+-(defun notmuch-tree-quit ()\r
++(defun notmuch-tree-quit (&optional forget-seen)\r
+   "Close the split view or exit tree."\r
+-  (interactive)\r
+-  (unless (notmuch-tree-close-message-window)\r
++  (interactive "P")\r
++  (unless (notmuch-tree-close-message-window forget-seen)\r
+     (kill-buffer (current-buffer))))\r
\r
+-(defun notmuch-tree-close-message-window ()\r
++(defun notmuch-tree-close-message-window (&optional forget-seen)\r
+   "Close the message-window. Return t if close succeeds."\r
+-  (interactive)\r
++  (interactive "P")\r
+   (when (and (window-live-p notmuch-tree-message-window)\r
+            (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer))\r
++    (unless notmuch-show-update-unread-on-seen\r
++      (with-selected-window notmuch-tree-message-window\r
++      (notmuch-show-mark-all-seen-read forget-seen)))\r
+     (delete-window notmuch-tree-message-window)\r
+     (unless (get-buffer-window-list notmuch-tree-message-buffer)\r
+       (kill-buffer notmuch-tree-message-buffer))\r
+@@ -784,6 +787,12 @@ This function inserts a collection of several complete threads as\r
+ passed to it by notmuch-tree-process-filter."\r
+   (mapc 'notmuch-tree-insert-forest-thread forest))\r
\r
++(defun notmuch-tree-command-hook ()\r
++  (when (eq major-mode 'notmuch-tree-mode)\r
++    (when (window-live-p notmuch-tree-message-window)\r
++      (with-selected-window notmuch-tree-message-window\r
++      (notmuch-show-command-hook)))))\r
++\r
+ (defun notmuch-tree-mode ()\r
+   "Major mode displaying messages (as opposed to threads) of of a notmuch search.\r
\r
+@@ -853,6 +862,7 @@ This is is a helper function for notmuch-tree. The arguments are\r
+ the same as for the function notmuch-tree."\r
+   (interactive)\r
+   (notmuch-tree-mode)\r
++  (add-hook 'post-command-hook #'notmuch-tree-command-hook nil t)\r
+   (setq notmuch-tree-basic-query basic-query)\r
+   (setq notmuch-tree-query-context query-context)\r
+   (setq notmuch-tree-target-msg target)\r
+-- \r
+1.7.9.1\r
+\r