]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
notmuch.el: Don't hide message bodies when all messages are read.
[notmuch] / notmuch.el
index 747f60c4dad7c0cddce7bd2ff33801eefa527e49..de44d24737fbb54ec75994fadb22a5227fe4a481 100644 (file)
     (define-key map "b" 'notmuch-show-toggle-body-read-visible)
     (define-key map "c" 'notmuch-show-toggle-citations-visible)
     (define-key map "h" 'notmuch-show-toggle-headers-visible)
-    (define-key map "n" 'notmuch-show-mark-read-then-next-message)
+    (define-key map "n" 'notmuch-show-next-message)
     (define-key map "p" 'notmuch-show-previous-message)
+    (define-key map (kbd "C-n") 'notmuch-show-next-line)
+    (define-key map (kbd "C-p") 'notmuch-show-previous-line)
     (define-key map "q" 'kill-this-buffer)
     (define-key map "s" 'notmuch-show-toggle-signatures-visible)
     (define-key map "x" 'kill-this-buffer)
 (defvar notmuch-show-id-regexp "ID: \\(.*\\)$")
 (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
 
+; XXX: This should be a generic function in emacs somewhere, not here
+(defun point-invisible-p ()
+  "Return whether the character at point is invisible.
+
+Here visibility is determined by `buffer-invisibility-spec' and
+the invisible property of any overlays for point. It doesn't have
+anything to do with whether point is currently being displayed
+within the current window."
+  (let ((prop (get-char-property (point) 'invisible)))
+    (if (eq buffer-invisibility-spec t)
+       prop
+      (or (memq prop buffer-invisibility-spec)
+         (assq prop buffer-invisibility-spec)))))
+
+(defun notmuch-show-next-line ()
+  "Like builtin `next-line' but ensuring we end on a visible character.
+
+By advancing forward until reaching a visible character.
+
+Unlike builtin `next-line' this version accepts no arguments."
+  (interactive)
+  (set 'this-command 'next-line)
+  (call-interactively 'next-line)
+  (while (point-invisible-p)
+    (forward-char)))
+
+(defun notmuch-show-previous-line ()
+  "Like builtin `previous-line' but ensuring we end on a visible character.
+
+By advancing forward until reaching a visible character.
+
+Unlike builtin `next-line' this version accepts no arguments."
+  (interactive)
+  (set 'this-command 'previous-line)
+  (call-interactively 'previous-line)
+  (while (point-invisible-p)
+    (forward-char)))
+
 (defun notmuch-show-get-message-id ()
   (save-excursion
     (beginning-of-line)
          (notmuch-show-set-tags (delete tag tags))))))
 
 (defun notmuch-show-archive-thread ()
-  "Archive each message currrently shown by removing the \"inbox\" tag from each.
+  "Archive each message in thread, and show next thread from search.
+
+Archive each message currrently shown by removing the \"inbox\"
+tag from each. Then kill this buffer and show the next thread
+from the search from which this thread was originally shown.
 
-This command is safe from any race condition of new messages
+Note: This command is safe from any race condition of new messages
 being delivered to the same thread. It does not archive the
 entire thread, but only the messages shown in the current
 buffer."
@@ -116,7 +160,13 @@ buffer."
       (if (not (eobp))
          (forward-char))
       (if (not (re-search-forward notmuch-show-message-begin-regexp nil t))
-         (goto-char (point-max))))))
+         (goto-char (point-max)))))
+  (let ((parent-buffer notmuch-show-parent-buffer))
+    (kill-this-buffer)
+    (if parent-buffer
+       (progn
+         (switch-to-buffer parent-buffer)
+         (notmuch-search-show-thread)))))
 
 (defun notmuch-show-move-to-current-message-summary-line ()
   "Move to the beginning of the one-line summary of the current message.
@@ -137,22 +187,36 @@ by searching backward)."
        (error "Not within a valid message."))
     (forward-line 2)))
 
+(defun notmuch-show-last-message-p ()
+  "Predicate testing whether point is within the last message."
+  (save-window-excursion
+    (save-excursion
+      (notmuch-show-move-to-current-message-summary-line)
+      (not (re-search-forward notmuch-show-message-begin-regexp nil t)))))
+
+(defun notmuch-show-message-unread-p ()
+  "Preficate testing whether current message is unread."
+  (member "unread" (notmuch-show-get-tags)))
+
 (defun notmuch-show-next-message ()
   "Advance to the beginning of the next message in the buffer.
 
-Moves to the beginning of the current message if already on the
-last message in the buffer."
+Moves to the last visible character of the current message if
+already on the last message in the buffer."
   (interactive)
   (notmuch-show-move-to-current-message-summary-line)
-  (re-search-forward notmuch-show-message-begin-regexp nil t)
-  (notmuch-show-move-to-current-message-summary-line)
+  (if (re-search-forward notmuch-show-message-begin-regexp nil t)
+      (notmuch-show-move-to-current-message-summary-line)
+    (goto-char (- (point-max) 1))
+    (while (point-invisible-p)
+      (backward-char)))
   (recenter 0))
 
 (defun notmuch-show-find-next-message ()
   "Returns the position of the next message in the buffer.
 
-Or the beginning of the current message if already within the last
-message in the buffer."
+Or the position of the last visible character of the current
+message if already within the last message in the buffer."
   ; save-excursion doesn't save our window position
   ; save-window-excursion doesn't save point
   ; Looks like we have to use both.
@@ -161,6 +225,18 @@ message in the buffer."
       (notmuch-show-next-message)
       (point))))
 
+(defun notmuch-show-next-unread-message ()
+  "Advance to the beginning of the next unread message in the buffer.
+
+Moves to the last visible character of the current message if
+there are no more unread messages past the current point."
+  (notmuch-show-next-message)
+  (while (and (not (notmuch-show-last-message-p))
+             (not (notmuch-show-message-unread-p)))
+    (notmuch-show-next-message))
+  (if (not (notmuch-show-message-unread-p))
+      (notmuch-show-next-message)))
+
 (defun notmuch-show-previous-message ()
   "Backup to the beginning of the previous message in the buffer.
 
@@ -178,11 +254,11 @@ simply move to the beginning of the current message."
          ))
     (recenter 0)))
 
-(defun notmuch-show-mark-read-then-next-message ()
-  "Remove unread tag from current message, then advance to next message."
+(defun notmuch-show-mark-read-then-next-unread-message ()
+  "Remove unread tag from current message, then advance to next unread message."
   (interactive)
   (notmuch-show-remove-tag "unread")
-  (notmuch-show-next-message))
+  (notmuch-show-next-unread-message))
 
 (defun notmuch-show-advance-marking-read-and-archiving ()
   "Advance through buffer, marking read and archiving.
@@ -194,8 +270,8 @@ If the current message in the thread is not yet fully visible,
 scroll by a near screenful to read more of the message.
 
 Otherwise, (the end of the current message is already within the
-current window), remove the \"unread\" tag from the current
-message and advance to the next message.
+current window), remove the \"unread\" tag (if present) from the
+current message and advance to the next message.
 
 Finally, if there is no further message to advance to, and this
 last message is already read, then archive the entire current
@@ -204,20 +280,14 @@ this buffer, and display the next thread from the search from
 which this thread was originally shown."
   (interactive)
   (let ((next (notmuch-show-find-next-message))
-       (unread (member "unread" (notmuch-show-get-tags))))
-    (if (and (not unread)
-            (equal next (point)))
-       (progn
-         (notmuch-show-archive-thread)
-         (let ((parent-buffer notmuch-show-parent-buffer))
-           (kill-this-buffer)
-           (if parent-buffer
-               (progn
-                 (switch-to-buffer parent-buffer)
-                 (notmuch-search-show-thread)))))
-      (if (< (notmuch-show-find-next-message) (window-end))
-         (notmuch-show-mark-read-then-next-message)
-       (scroll-up nil)))))
+       (unread (notmuch-show-message-unread-p)))
+    (if (> next (window-end))
+       (scroll-up nil)
+      (if unread
+         (notmuch-show-mark-read-then-next-unread-message)
+       (if (notmuch-show-last-message-p)
+           (notmuch-show-archive-thread)
+         (notmuch-show-next-unread-message))))))
 
 (defun notmuch-show-markup-citations-region (beg end)
   (goto-char beg)
@@ -250,7 +320,7 @@ which this thread was originally shown."
     (re-search-forward notmuch-show-body-end-regexp)
     (let ((end (match-beginning 0)))
       (notmuch-show-markup-citations-region beg end)
-      (if (not (member "unread" (notmuch-show-get-tags)))
+      (if (not (notmuch-show-message-unread-p))
          (overlay-put (make-overlay beg end)
                       'invisible 'notmuch-show-body-read)))))
 
@@ -395,12 +465,23 @@ thread from that buffer can be show when done with this one)."
        (call-process "notmuch" nil t nil "show" thread-id)
        (notmuch-show-markup-messages)
        )
+      ; Move straight to the first unread message
+      (if (not (notmuch-show-message-unread-p))
+         (progn
+           (notmuch-show-next-unread-message)
+           ; But if there are no unread messages, go back to the
+           ; beginning of the buffer, and open up the bodies of all
+           ; read message.
+           (if (not (notmuch-show-message-unread-p))
+               (progn
+                 (goto-char (point-min))
+                 (notmuch-show-toggle-body-read-visible)))))
       )))
 
 (defvar notmuch-search-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map "a" 'notmuch-search-archive-thread)
-    (define-key map "b" 'scroll-down)
+    (define-key map "b" 'notmuch-search-scroll-down)
     (define-key map "f" 'notmuch-search-filter)
     (define-key map "n" 'next-line)
     (define-key map "p" 'previous-line)
@@ -415,17 +496,39 @@ thread from that buffer can be show when done with this one)."
     (define-key map ">" 'notmuch-search-goto-last-thread)
     (define-key map "=" 'notmuch-search-refresh-view)
     (define-key map "\M->" 'notmuch-search-goto-last-thread)
-    (define-key map " " 'scroll-up)
-    (define-key map (kbd "<DEL>") 'scroll-down)
+    (define-key map " " 'notmuch-search-scroll-up)
+    (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
 
+(defun notmuch-search-scroll-up ()
+  "Scroll up, moving point to last message in thread if at end."
+  (interactive)
+  (condition-case nil
+      (scroll-up nil)
+    ((end-of-buffer) (notmuch-search-goto-last-thread))))
+
+(defun notmuch-search-scroll-down ()
+  "Scroll down, moving point to first message in thread if at beginning."
+  (interactive)
+  ; I don't know why scroll-down doesn't signal beginning-of-buffer
+  ; the way that scroll-up signals end-of-buffer, but c'est la vie.
+  ;
+  ; So instead of trapping a signal we instead check whether the
+  ; window begins on the first line of the buffer and if so, move
+  ; directly to that position. (We have to count lines since the
+  ; window-start position is not the same as point-min due to the
+  ; invisible thread-ID characters on the first line.
+  (if (equal (count-lines (point-min) (window-start)) 1)
+      (goto-char (window-start))
+    (scroll-down nil)))
+
 (defun notmuch-search-goto-last-thread (&optional arg)
   "Move point to the last thread in the buffer."
   (interactive "^P")
   (end-of-buffer arg)
-  (beginning-of-line))
+  (forward-line -1))
 
 ;;;###autoload
 (defun notmuch-search-mode ()
@@ -494,7 +597,9 @@ global search.
   (interactive)
   (let ((thread-id (notmuch-search-find-thread-id)))
     (forward-line)
-    (notmuch-show thread-id (current-buffer))))
+    (if (> (length thread-id) 0)
+       (notmuch-show thread-id (current-buffer))
+      (error "End of search results"))))
 
 (defun notmuch-call-notmuch-process (&rest args)
   (let ((error-buffer (get-buffer-create "*Notmuch errors*")))
@@ -566,12 +671,6 @@ This function advances point to the next line when finished."
       (save-excursion
        (call-process "notmuch" nil t nil "search" query)
        (notmuch-search-markup-thread-ids)
-        ; A well-behaved program ends its output with a newline, but we
-        ; don't actually want the blank line at the end of the file.
-       (goto-char (point-max))
-       (if (looking-at "^$")
-           (delete-backward-char 1)
-         )
        ))))
 
 (defun notmuch-search-refresh-view ()