]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
emacs: Fix backspace to not scroll more than the previous message
[notmuch] / notmuch.el
index 040997eeaf00eaf4e1cf5a3c604a8fcdf0bb9f71..c611dcc2cc72ebde11568b3e42f2fa6bb0f70f1c 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)
@@ -225,6 +241,62 @@ Unlike builtin `previous-line' this version accepts no arguments."
     (re-search-forward notmuch-show-tags-regexp)
     (split-string (buffer-substring (match-beginning 1) (match-end 1)))))
 
+(defun notmuch-show-get-bcc ()
+  "Return BCC address(es) of current message"
+  (notmuch-show-get-header-field 'bcc))
+
+(defun notmuch-show-get-cc ()
+  "Return CC address(es) of current message"
+  (notmuch-show-get-header-field 'cc))
+
+(defun notmuch-show-get-date ()
+  "Return Date of current message"
+  (notmuch-show-get-header-field 'date))
+
+(defun notmuch-show-get-from ()
+  "Return From address of current message"
+  (notmuch-show-get-header-field 'from))
+
+(defun notmuch-show-get-subject ()
+  "Return Subject of current message"
+  (notmuch-show-get-header-field 'subject))
+
+(defun notmuch-show-get-to ()
+  "Return To address(es) of current message"
+  (notmuch-show-get-header-field 'to))
+
+(defun notmuch-show-get-header-field (name)
+  "Retrieve the header field NAME from the current message.
+NAME should be a symbol, in lower case, as returned by
+mail-header-extract-no-properties"
+  (let* ((result (assoc name (notmuch-show-get-header)))
+        (val (and result (cdr result))))
+    val))
+
+(defun notmuch-show-get-header ()
+  "Retrieve and parse the header from the current message. Returns an alist with of (header . value)
+where header is a symbol and value is a string.  The summary from notmuch-show is returned as the
+pseudoheader summary"
+  (require 'mailheader)
+  (save-excursion
+    (beginning-of-line)
+    (if (not (looking-at notmuch-show-message-begin-regexp))
+       (re-search-backward notmuch-show-message-begin-regexp))
+    (re-search-forward (concat notmuch-show-header-begin-regexp "\n[[:space:]]*\\(.*\\)\n"))
+    (let* ((summary (buffer-substring-no-properties (match-beginning 1) (match-end 1)))
+         (beg (point)))
+      (re-search-forward notmuch-show-header-end-regexp)
+      (let ((text (buffer-substring beg (match-beginning 0))))
+       (with-temp-buffer
+         (insert text)
+         (goto-char (point-min))
+         (while (looking-at "\\([[:space:]]*\\)[A-Za-z][-A-Za-z0-9]*:")
+           (delete-region (match-beginning 1) (match-end 1))
+           (forward-line)
+           )
+         (goto-char (point-min))
+         (cons (cons 'summary summary) (mail-header-extract-no-properties)))))))
+
 (defun notmuch-show-add-tag (&rest toadd)
   "Add a tag to the current message."
   (interactive
@@ -248,13 +320,22 @@ Unlike builtin `previous-line' this version accepts no arguments."
                         (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))
@@ -267,47 +348,12 @@ Unlike builtin `previous-line' this version accepts no arguments."
          (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)
@@ -459,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
@@ -479,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.
 
@@ -489,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))
@@ -523,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.
@@ -537,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
@@ -557,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))
@@ -565,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:
@@ -578,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
@@ -592,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))))))
 
@@ -733,7 +820,7 @@ is what to put on the button."
         (let (mime-message mime-type)
           (save-excursion
             (re-search-forward notmuch-show-contentype-regexp end t)
-            (setq mime-type (car (split-string (buffer-substring 
+            (setq mime-type (car (split-string (buffer-substring
                                                 (match-beginning 1) (match-end 1))))))
 
           (if (equal mime-type "text/html")
@@ -778,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
@@ -800,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)
@@ -944,8 +1031,12 @@ For a mouse binding, return nil."
     (if (mouse-event-p key)
        nil
       (if (keymapp action)
-         (let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key))))
-           (mapconcat substitute (cdr action) "\n"))
+         (let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key)))
+               (as-list))
+           (map-keymap (lambda (a b)
+                         (push (cons a b) as-list))
+                       action)
+           (mapconcat substitute as-list "\n"))
        (concat prefix (format-kbd-macro (vector key))
                "\t"
                (notmuch-documentation-first-line action))))))
@@ -988,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]'.
@@ -1030,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()
@@ -1350,7 +1487,7 @@ This function advances the next thread when finished."
                  (more t)
                  (inhibit-read-only t))
              (while more
-               (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\(.*\\) \\(\\[[0-9/]*\\]\\) \\([^:]*\\); \\(.*\\) (\\([^()]*\\))$" string line)
+               (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\(.*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line)
                    (let* ((thread-id (match-string 1 string))
                           (date (match-string 2 string))
                           (count (match-string 3 string))