--- /dev/null
+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