]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
notmuch.el: Make notmuch-search scroll commands move to first/last message.
[notmuch] / notmuch.el
index 16ca037ed5573757280116f82e04b72503c856ed..de995ecaa014294ddf0f1cd0caeac6cfc6708c2d 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)
@@ -147,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.
@@ -171,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.
 
@@ -188,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.
@@ -204,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
@@ -214,13 +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)))
-       (notmuch-show-archive-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)
@@ -253,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)))))
 
@@ -398,12 +465,20 @@ 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.
+           (if (not (notmuch-show-message-unread-p))
+               (goto-char (point-min)))))
       )))
 
 (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)
@@ -418,12 +493,34 @@ 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")