]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
emacs: Fix refresh of search results to leave cursor on current thread.
[notmuch] / notmuch.el
index 82471b57dc9b4cabb7271e73b61e4a80a4ada3a9..5b553bbf55050be5b11a61221a28fb7c855e6ed7 100644 (file)
 (require 'mm-view)
 (require 'message)
 
+(defvar notmuch-show-stash-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "c" 'notmuch-show-stash-cc)
+    (define-key map "d" 'notmuch-show-stash-date)
+    (define-key map "F" 'notmuch-show-stash-filename)
+    (define-key map "f" 'notmuch-show-stash-from)
+    (define-key map "i" 'notmuch-show-stash-message-id)
+    (define-key map "s" 'notmuch-show-stash-subject)
+    (define-key map "T" 'notmuch-show-stash-tags)
+    (define-key map "t" 'notmuch-show-stash-to)
+    map)
+  "Submap for stash commands"
+  )
+
+(fset 'notmuch-show-stash-map notmuch-show-stash-map)
+
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map "?" 'notmuch-help)
     (define-key map "w" 'notmuch-show-save-attachments)
     (define-key map "V" 'notmuch-show-view-raw-message)
     (define-key map "v" 'notmuch-show-view-all-mime-parts)
+    (define-key map "c" 'notmuch-show-stash-map)
     (define-key map "b" 'notmuch-show-toggle-current-body)
     (define-key map "h" 'notmuch-show-toggle-current-header)
     (define-key map "-" 'notmuch-show-remove-tag)
     (define-key map "+" 'notmuch-show-add-tag)
-    (define-key map "X" 'notmuch-show-mark-read-then-archive-then-exit)
     (define-key map "x" 'notmuch-show-archive-thread-then-exit)
-    (define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
     (define-key map "a" 'notmuch-show-archive-thread)
-    (define-key map "p" 'notmuch-show-previous-message)
-    (define-key map "N" 'notmuch-show-mark-read-then-next-open-message)
-    (define-key map "n" 'notmuch-show-next-message)
+    (define-key map "P" 'notmuch-show-previous-message)
+    (define-key map "N" 'notmuch-show-next-message)
+    (define-key map "p" 'notmuch-show-previous-open-message)
+    (define-key map "n" 'notmuch-show-next-open-message)
     (define-key map (kbd "DEL") 'notmuch-show-rewind)
-    (define-key map " " 'notmuch-show-advance-marking-read-and-archiving)
+    (define-key map " " 'notmuch-show-advance-and-archive)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
@@ -304,13 +320,22 @@ pseudoheader summary"
                         (cons (notmuch-show-get-message-id) nil)))
          (notmuch-show-set-tags (sort (set-difference tags toremove :test 'string=) 'string<))))))
 
-(defun notmuch-show-archive-thread-maybe-mark-read (markread)
+(defun notmuch-show-archive-thread ()
+  "Archive each message in thread, then show next thread from search.
+
+Archive each message currently 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.
+
+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."
+  (interactive)
   (save-excursion
     (goto-char (point-min))
     (while (not (eobp))
-      (if markread
-         (notmuch-show-remove-tag "unread" "inbox")
-       (notmuch-show-remove-tag "inbox"))
+      (notmuch-show-remove-tag "inbox")
       (if (not (eobp))
          (forward-char))
       (if (not (re-search-forward notmuch-show-message-begin-regexp nil t))
@@ -323,47 +348,12 @@ pseudoheader summary"
          (forward-line)
          (notmuch-search-show-thread)))))
 
-(defun notmuch-show-mark-read-then-archive-thread ()
-  "Remove unread tags from thread, then archive and show next thread.
-
-Archive each message currently shown by removing the \"unread\"
-and \"inbox\" tag from each. Then kill this buffer and show the
-next thread from the search from which this thread was originally
-shown.
-
-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."
-  (interactive)
-  (notmuch-show-archive-thread-maybe-mark-read t))
-
-(defun notmuch-show-archive-thread ()
-  "Archive each message in thread, then show next thread from search.
-
-Archive each message currently 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.
-
-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."
-  (interactive)
-  (notmuch-show-archive-thread-maybe-mark-read nil))
-
 (defun notmuch-show-archive-thread-then-exit ()
   "Archive each message in thread, then exit back to search results."
   (interactive)
   (notmuch-show-archive-thread)
   (kill-this-buffer))
 
-(defun notmuch-show-mark-read-then-archive-then-exit ()
-  "Remove unread tags from thread, then archive and exit to search results."
-  (interactive)
-  (notmuch-show-mark-read-then-archive-thread)
-  (kill-this-buffer))
-
 (defun notmuch-show-view-raw-message ()
   "View the raw email of the current message."
   (interactive)
@@ -515,14 +505,13 @@ by searching backward)."
       (setq btn (previous-button (button-start btn))))
     (not (invisible-p (button-get btn 'invisibility-spec)))))
 
-(defun notmuch-show-next-message ()
+(defun notmuch-show-next-message-without-marking-read ()
   "Advance to the beginning of the next message in the buffer.
 
 Moves to the last visible character of the current message if
 already on the last message in the buffer.
 
 Returns nil if already on the last message in the buffer."
-  (interactive)
   (notmuch-show-move-to-current-message-summary-line)
   (if (re-search-forward notmuch-show-message-begin-regexp nil t)
       (progn
@@ -535,6 +524,18 @@ Returns nil if already on the last message in the buffer."
     (recenter 0)
     nil))
 
+(defun notmuch-show-next-message ()
+  "Advance to the next message (whether open or closed)
+and remove the unread tag from that message.
+
+Moves to the last visible character of the current message if
+already on the last message in the buffer.
+
+Returns nil if already on the last message in the buffer."
+  (interactive)
+  (notmuch-show-next-message-without-marking-read)
+  (notmuch-show-mark-read))
+
 (defun notmuch-show-find-next-message ()
   "Returns the position of the next message in the buffer.
 
@@ -545,32 +546,39 @@ message if already within the last message in the buffer."
   ; Looks like we have to use both.
   (save-excursion
     (save-window-excursion
-      (notmuch-show-next-message)
+      (notmuch-show-next-message-without-marking-read)
       (point))))
 
 (defun notmuch-show-next-unread-message ()
-  "Advance to the beginning of the next unread message in the buffer.
+  "Advance to the next unread message.
 
 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)
+  (notmuch-show-next-message-without-marking-read)
   (while (and (not (notmuch-show-last-message-p))
              (not (notmuch-show-message-unread-p)))
-    (notmuch-show-next-message))
+    (notmuch-show-next-message-without-marking-read))
   (if (not (notmuch-show-message-unread-p))
-      (notmuch-show-next-message)))
+      (notmuch-show-next-message-without-marking-read))
+  (notmuch-show-mark-read))
 
 (defun notmuch-show-next-open-message ()
-  "Advance to the next open message (that is, body is not invisible)."
-  (while (and (notmuch-show-next-message)
-             (not (notmuch-show-message-open-p)))))
+  "Advance to the next open message (that is, body is visible).
 
-(defun notmuch-show-previous-message ()
+Moves to the last visible character of the final message in the buffer
+if there are no more open messages."
+  (interactive)
+  (while (and (notmuch-show-next-message-without-marking-read)
+             (not (notmuch-show-message-open-p))))
+  (notmuch-show-mark-read))
+
+(defun notmuch-show-previous-message-without-marking-read ()
   "Backup to the beginning of the previous message in the buffer.
 
 If within a message rather than at the beginning of it, then
-simply move to the beginning of the current message."
-  (interactive)
+simply move to the beginning of the current message.
+
+Returns nil if already on the first message in the buffer."
   (let ((start (point)))
     (notmuch-show-move-to-current-message-summary-line)
     (if (not (< (point) start))
@@ -579,8 +587,22 @@ simply move to the beginning of the current message."
          (re-search-backward notmuch-show-message-begin-regexp nil t)
          (re-search-backward notmuch-show-message-begin-regexp nil t)
          (notmuch-show-move-to-current-message-summary-line)
-         ))
-    (recenter 0)))
+         (recenter 0)
+         (if (= (point) start)
+             nil
+           t))
+      (recenter 0)
+      nil)))
+
+(defun notmuch-show-previous-message ()
+  "Backup to the previous message (whether open or closed)
+and remove the unread tag from that message.
+
+If within a message rather than at the beginning of it, then
+simply move to the beginning of the current message."
+  (interactive)
+  (notmuch-show-previous-message-without-marking-read)
+  (notmuch-show-mark-read))
 
 (defun notmuch-show-find-previous-message ()
   "Returns the position of the previous message in the buffer.
@@ -593,17 +615,21 @@ it."
   ; Looks like we have to use both.
   (save-excursion
     (save-window-excursion
-      (notmuch-show-previous-message)
+      (notmuch-show-previous-message-without-marking-read)
       (point))))
 
-(defun notmuch-show-mark-read-then-next-open-message ()
-  "Remove unread tag from this message, then advance to next open message."
+(defun notmuch-show-previous-open-message ()
+  "Backup to previous open message (that is, body is visible).
+
+Moves to the first message in the buffer if there are no previous
+open messages."
   (interactive)
-  (notmuch-show-remove-tag "unread")
-  (notmuch-show-next-open-message))
+  (while (and (notmuch-show-previous-message-without-marking-read)
+             (not (notmuch-show-message-open-p))))
+  (notmuch-show-mark-read))
 
 (defun notmuch-show-rewind ()
-  "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-marking-read-and-archiving]).
+  "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
@@ -613,7 +639,7 @@ Otherwise, just scroll down a screenful of the current message.
 
 This command does not modify any message tags, (it does not undo
 any effects from previous calls to
-`notmuch-show-advance-marking-read-and-archiving'."
+`notmuch-show-advance-and-archive'."
   (interactive)
   (let ((previous (notmuch-show-find-previous-message)))
     (if (> (count-lines previous (point)) (- (window-height) next-screen-context-lines))
@@ -621,11 +647,17 @@ any effects from previous calls to
          (condition-case nil
              (scroll-down nil)
            ((beginning-of-buffer) nil))
-         (goto-char (window-start)))
+         (goto-char (window-start))
+         ; Because count-lines counts invivisible lines, we may have
+         ; scrolled to far. If so., notice this and fix it up.
+         (if (< (point) previous)
+             (progn
+               (goto-char previous)
+               (recenter 0))))
       (notmuch-show-previous-message))))
 
-(defun notmuch-show-advance-marking-read-and-archiving ()
-  "Advance through thread, marking read and archiving.
+(defun notmuch-show-advance-and-archive ()
+  "Advance through thread and archive.
 
 This command is intended to be one of the simplest ways to
 process a thread of email. It does the following:
@@ -634,8 +666,7 @@ 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 (if present) from the
-current message and advance to the next open message.
+current window), advance to the next open message.
 
 Finally, if there is no further message to advance to, and this
 last message is already read, then archive the entire current
@@ -648,7 +679,7 @@ which this thread was originally shown."
     (if (> next (window-end))
        (scroll-up nil)
       (let ((last (notmuch-show-last-message-p)))
-       (notmuch-show-mark-read-then-next-open-message)
+       (notmuch-show-next-open-message)
        (if last
            (notmuch-show-archive-thread))))))
 
@@ -834,8 +865,8 @@ is what to put on the button."
 
 (defun notmuch-show-markup-body (depth match btn)
   "Markup a message body, (indenting, buttonizing citations,
-etc.), and conditionally hiding the body itself if the message
-has been read and does not match the current search.
+etc.), and hiding the body itself if the message does not match
+the current search.
 
 DEPTH specifies the depth at which this message appears in the
 tree of the current thread, (the top-level messages have depth 0
@@ -856,7 +887,7 @@ before the delimiter marking the beginning of the body."
         (overlay-put (make-overlay beg end)
                      'invisible invis-spec)
         (button-put btn 'invisibility-spec invis-spec)
-        (if (not (or (notmuch-show-message-unread-p) match))
+        (if (not match)
             (add-to-invisibility-spec invis-spec)))
       (set-marker beg nil)
       (set-marker end nil)
@@ -1048,17 +1079,15 @@ pressing RET after positioning the cursor on a hidden part, (for
 which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
 
 Reading the thread sequentially is well-supported by pressing
-\\[notmuch-show-advance-marking-read-and-archiving]. This will scroll the current message (if necessary),
-advance to the next message, or advance to the next thread (if
-already on the last message of a thread). As each message is
-scrolled away its \"unread\" tag will be removed, and as each
-thread is scrolled away the \"inbox\" tag will be removed from
-each message in the thread.
+\\[notmuch-show-advance-and-archive]. This will
+scroll the current message (if necessary), advance to the next
+message, or advance to the next thread (if already on the last
+message of a thread).
 
 Other commands are available to read or manipulate the thread more
 selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages without
 removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread without
-scrolling through with \\[notmuch-show-advance-marking-read-and-archiving]).
+scrolling through with \\[notmuch-show-advance-and-archive]).
 
 You can add or remove arbitary tags from the current message with
 '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
@@ -1090,12 +1119,60 @@ All currently available key bindings:
   :options '(hl-line-mode)
   :group 'notmuch)
 
+(defun notmuch-show-do-stash (text)
+    (kill-new text)
+    (message (concat "Saved: " text)))
+
+(defun notmuch-show-stash-cc ()
+  "Copy CC field of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-cc)))
+
+(defun notmuch-show-stash-date ()
+  "Copy date of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-date)))
+
+(defun notmuch-show-stash-filename ()
+  "Copy filename of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-filename)))
+
+(defun notmuch-show-stash-from ()
+  "Copy From address of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-from)))
+
+(defun notmuch-show-stash-message-id ()
+  "Copy message ID of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-message-id)))
+
+(defun notmuch-show-stash-subject ()
+  "Copy Subject field of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-subject)))
+
+(defun notmuch-show-stash-tags ()
+  "Copy tags of current message to kill-ring as a comma separated list."
+  (interactive)
+  (notmuch-show-do-stash (mapconcat 'identity (notmuch-show-get-tags) ",")))
+
+(defun notmuch-show-stash-to ()
+  "Copy To address of current message to kill-ring."
+  (interactive)
+  (notmuch-show-do-stash (notmuch-show-get-to)))
+
 ; Make show mode a bit prettier, highlighting URLs and using word wrap
 
+(defun notmuch-show-mark-read ()
+  (notmuch-show-remove-tag "unread"))
+
 (defun notmuch-show-pretty-hook ()
   (goto-address-mode 1)
   (visual-line-mode))
 
+(add-hook 'notmuch-show-hook 'notmuch-show-mark-read)
 (add-hook 'notmuch-show-hook 'notmuch-show-pretty-hook)
 (add-hook 'notmuch-search-hook
          (lambda()
@@ -1169,6 +1246,8 @@ matching this search term are shown if non-nil. "
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
 
 (defvar notmuch-search-query-string)
+(defvar notmuch-search-target-thread)
+(defvar notmuch-search-target-position)
 (defvar notmuch-search-oldest-first t
   "Show the oldest mail first in the search-mode")
 
@@ -1269,6 +1348,8 @@ Complete list of currently available key bindings:
   (kill-all-local-variables)
   (make-local-variable 'notmuch-search-query-string)
   (make-local-variable 'notmuch-search-oldest-first)
+  (make-local-variable 'notmuch-search-target-thread)
+  (make-local-variable 'notmuch-search-target-position)
   (set (make-local-variable 'scroll-preserve-screen-position) t)
   (add-to-invisibility-spec 'notmuch-search)
   (use-local-map notmuch-search-mode-map)
@@ -1365,8 +1446,7 @@ which match the current search terms."
 (defun notmuch-search-remove-tag (tag)
   "Remove a tag from the currently selected thread.
 
-The tag is removed from messages in the currently selected thread
-which match the current search terms."
+The tag is removed from all messages in the currently selected thread."
   (interactive
    (list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-search-find-thread-id))))
   (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id))
@@ -1384,12 +1464,14 @@ This function advances the next thread when finished."
   "Add a message to let user know when \"notmuch search\" exits"
   (let ((buffer (process-buffer proc))
        (status (process-status proc))
-       (exit-status (process-exit-status proc)))
+       (exit-status (process-exit-status proc))
+       (never-found-target-thread nil))
     (if (memq status '(exit signal))
        (if (buffer-live-p buffer)
            (with-current-buffer buffer
              (save-excursion
-               (let ((inhibit-read-only t))
+               (let ((inhibit-read-only t)
+                     (atbob (bobp)))
                  (goto-char (point-max))
                  (if (eq status 'signal)
                      (insert "Incomplete search results (search process was killed).\n"))
@@ -1398,11 +1480,17 @@ This function advances the next thread when finished."
                        (insert "End of search results.")
                        (if (not (= exit-status 0))
                            (insert (format " (process returned %d)" exit-status)))
-                       (insert "\n"))))))))))
+                       (insert "\n")
+                       (if (and atbob
+                                notmuch-search-target-thread)
+                           (set 'never-found-target-thread t))))))
+             (if never-found-target-thread
+                 (goto-char notmuch-search-target-position)))))))
 
 (defun notmuch-search-process-filter (proc string)
   "Process and filter the output of \"notmuch search\""
-  (let ((buffer (process-buffer proc)))
+  (let ((buffer (process-buffer proc))
+       (found-target nil))
     (if (buffer-live-p buffer)
        (with-current-buffer buffer
          (save-excursion
@@ -1425,9 +1513,16 @@ This function advances the next thread when finished."
                        (insert (format "%s %-7s %-40s %s (%s)\n" date count authors subject tags))
                        (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id)
                        (put-text-property beg (point-marker) 'notmuch-search-authors authors)
-                       (put-text-property beg (point-marker) 'notmuch-search-subject subject))
+                       (put-text-property beg (point-marker) 'notmuch-search-subject subject)
+                       (if (and notmuch-search-target-thread
+                                (string= thread-id notmuch-search-target-thread))
+                           (progn
+                             (set 'found-target beg)
+                             (set 'notmuch-search-target-thread nil))))
                      (set 'line (match-end 0)))
-                 (set 'more nil))))))
+                 (set 'more nil)))))
+         (if found-target
+             (goto-char found-target)))
       (delete-process proc))))
 
 (defun notmuch-search-operate-all (action)
@@ -1454,14 +1549,27 @@ characters as well as `_.+-'.
           (append action-split (list notmuch-search-query-string) nil))))
 
 ;;;###autoload
-(defun notmuch-search (query &optional oldest-first)
-  "Run \"notmuch search\" with the given query string and display results."
+(defun notmuch-search (query &optional oldest-first target-thread target-position)
+  "Run \"notmuch search\" with the given query string and display results.
+
+The optional parameters are used as follows:
+
+  oldest-first: A Boolean controlling the sort order of returned threads
+  target-thread: A thread ID (with the thread: prefix) that will be made
+                 current if it appears in the search results.
+  saved-position: If the search results complete, and the target thread is
+                  not found in the results, and the point is still at the
+                  beginning of the buffer, then the point will be moved to
+                  the saved position.
+"
   (interactive "sNotmuch search: ")
   (let ((buffer (get-buffer-create (concat "*notmuch-search-" query "*"))))
     (switch-to-buffer buffer)
     (notmuch-search-mode)
     (set 'notmuch-search-query-string query)
     (set 'notmuch-search-oldest-first oldest-first)
+    (set 'notmuch-search-target-thread target-thread)
+    (set 'notmuch-search-target-position target-position)
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
       (if proc
@@ -1492,11 +1600,9 @@ same relative position within the new buffer."
        (thread (notmuch-search-find-thread-id))
        (query notmuch-search-query-string))
     (kill-this-buffer)
-    (notmuch-search query oldest-first)
+    (notmuch-search query oldest-first thread here)
     (goto-char (point-min))
-    (if (re-search-forward (concat "^" thread) nil t)
-       (beginning-of-line)
-      (goto-char here))))
+    ))
 
 (defun notmuch-search-toggle-order ()
   "Toggle the current search order.