]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
notmuch.el: Handle attached images via an external viewer.
[notmuch] / notmuch.el
index c2e0899e5c8e7a1787c179439a530a6952127423..040997eeaf00eaf4e1cf5a3c604a8fcdf0bb9f71 100644 (file)
@@ -94,9 +94,27 @@ for indentation at the beginning of the line. But notmuch will
 move past the indentation when testing this pattern, (so that the
 pattern can still test against the entire line).")
 
 move past the indentation when testing this pattern, (so that the
 pattern can still test against the entire line).")
 
+(defvar notmuch-show-signature-button-format
+  "[ %d-line signature. Click/Enter to toggle visibility. ]"
+  "String used to construct button text for hidden signatures
+
+Can use up to one integer format parameter, i.e. %d")
+
+(defvar notmuch-show-citation-button-format
+  "[ %d more citation lines. Click/Enter to toggle visibility. ]"
+  "String used to construct button text for hidden citations.
+
+Can use up to one integer format parameter, i.e. %d")
+
 (defvar notmuch-show-signature-lines-max 12
   "Maximum length of signature that will be hidden by default.")
 
 (defvar notmuch-show-signature-lines-max 12
   "Maximum length of signature that will be hidden by default.")
 
+(defvar notmuch-show-citation-lines-prefix 4
+  "Always show at least this many lines of a citation.
+
+If there is one more line, show that, otherwise collapse
+remaining lines into a button.")
+
 (defvar notmuch-command "notmuch"
   "Command to run the notmuch binary.")
 
 (defvar notmuch-command "notmuch"
   "Command to run the notmuch binary.")
 
@@ -115,6 +133,8 @@ pattern can still test against the entire line).")
 (defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)")
 (defvar notmuch-show-depth-match-regexp " depth:\\([0-9]*\\).*match:\\([01]\\) ")
 (defvar notmuch-show-filename-regexp "filename:\\(.*\\)$")
 (defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)")
 (defvar notmuch-show-depth-match-regexp " depth:\\([0-9]*\\).*match:\\([01]\\) ")
 (defvar notmuch-show-filename-regexp "filename:\\(.*\\)$")
+(defvar notmuch-show-contentype-regexp "Content-type: \\(.*\\)")
+
 (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
 
 (defvar notmuch-show-parent-buffer nil)
 (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
 
 (defvar notmuch-show-parent-buffer nil)
@@ -301,13 +321,28 @@ buffer."
          (with-current-buffer buf
            (insert-file-contents filename nil nil nil t)
            ,@body)
          (with-current-buffer buf
            (insert-file-contents filename nil nil nil t)
            ,@body)
-        (kill-buffer buf)))))
+        (kill-buffer buf)))))
 
 (defun notmuch-show-view-all-mime-parts ()
   "Use external viewers to view all attachments from the current message."
   (interactive)
   (with-current-notmuch-show-message
 
 (defun notmuch-show-view-all-mime-parts ()
   "Use external viewers to view all attachments from the current message."
   (interactive)
   (with-current-notmuch-show-message
-   (mm-display-parts (mm-dissect-buffer))))
+   ; We ovverride the mm-inline-media-tests to indicate which message
+   ; parts are already sufficiently handled by the original
+   ; presentation of the message in notmuch-show mode. These parts
+   ; will be inserted directly into the temporary buffer of
+   ; with-current-notmuch-show-message and silently discarded.
+   ;
+   ; Any MIME part not explicitly mentioned here will be handled by an
+   ; external viewer as configured in the various mailcap files.
+   (let ((mm-inline-media-tests '(
+                                 ("text/.*" ignore identity)
+                                 ("application/pgp-signature" ignore identity)
+                                 ("multipart/alternative" ignore identity)
+                                 ("multipart/mixed" ignore identity)
+                                 ("multipart/related" ignore identity)
+                                )))
+     (mm-display-parts (mm-dissect-buffer)))))
 
 (defun notmuch-foreach-mime-part (function mm-handle)
   (cond ((stringp (car mm-handle))
 
 (defun notmuch-foreach-mime-part (function mm-handle)
   (cond ((stringp (car mm-handle))
@@ -564,12 +599,12 @@ which this thread was originally shown."
 (defun notmuch-show-next-button ()
   "Advance point to the next button in the buffer."
   (interactive)
 (defun notmuch-show-next-button ()
   "Advance point to the next button in the buffer."
   (interactive)
-  (goto-char (button-start (next-button (point)))))
+  (forward-button 1))
 
 (defun notmuch-show-previous-button ()
   "Move point back to the previous button in the buffer."
   (interactive)
 
 (defun notmuch-show-previous-button ()
   "Move point back to the previous button in the buffer."
   (interactive)
-  (goto-char (button-start (previous-button (point)))))
+  (backward-button 1))
 
 (defun notmuch-toggle-invisible-action (cite-button)
   (let ((invis-spec (button-get cite-button 'invisibility-spec)))
 
 (defun notmuch-toggle-invisible-action (cite-button)
   (let ((invis-spec (button-get cite-button 'invisibility-spec)))
@@ -604,7 +639,7 @@ which this thread was originally shown."
 (define-button-type 'notmuch-button-invisibility-toggle-type
   'action 'notmuch-toggle-invisible-action
   'follow-link t
 (define-button-type 'notmuch-button-invisibility-toggle-type
   'action 'notmuch-toggle-invisible-action
   'follow-link t
-  'face "default")
+  'face 'font-lock-comment-face)
 (define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation"
   :supertype 'notmuch-button-invisibility-toggle-type)
 (define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature"
 (define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation"
   :supertype 'notmuch-button-invisibility-toggle-type)
 (define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature"
@@ -616,71 +651,123 @@ which this thread was originally shown."
   'face 'notmuch-message-summary-face
   :supertype 'notmuch-button-invisibility-toggle-type)
 
   'face 'notmuch-message-summary-face
   :supertype 'notmuch-button-invisibility-toggle-type)
 
+(defun notmuch-show-citation-regexp (depth)
+  "Build a regexp for matching citations at a given DEPTH (indent)"
+  (let ((line-regexp (format "[[:space:]]\\{%d\\}>.*\n" depth)))
+    (concat "\\(?:^" line-regexp
+           "\\(?:[[:space:]]*\n" line-regexp
+           "\\)?\\)+")))
+
+(defun notmuch-show-region-to-button (beg end type prefix button-text)
+  "Auxilary function to do the actual making of overlays and buttons
+
+BEG and END are buffer locations. TYPE should a string, either
+\"citation\" or \"signature\". PREFIX is some arbitrary text to
+insert before the button, probably for indentation.  BUTTON-TEXT
+is what to put on the button."
+
+;; This uses some slightly tricky conversions between strings and
+;; symbols because of the way the button code works. Note that
+;; replacing intern-soft with make-symbol will cause this to fail,
+;; since the newly created symbol has no plist.
+
+  (let ((overlay (make-overlay beg end))
+       (invis-spec (make-symbol (concat "notmuch-" type "-region")))
+       (button-type (intern-soft (concat "notmuch-button-"
+                                         type "-toggle-type"))))
+    (add-to-invisibility-spec invis-spec)
+    (overlay-put overlay 'invisible invis-spec)
+    (goto-char (1+ end))
+    (save-excursion
+      (goto-char (1- beg))
+      (insert prefix)
+      (insert-button button-text
+                    'invisibility-spec invis-spec
+                    :type button-type)
+      )))
+
+
 (defun notmuch-show-markup-citations-region (beg end depth)
 (defun notmuch-show-markup-citations-region (beg end depth)
-  (goto-char beg)
-  (beginning-of-line)
-  (while (< (point) end)
-    (let ((beg-sub (point-marker))
-         (indent (make-string depth ? ))
-         (citation ">"))
-      (move-to-column depth)
-      (if (looking-at citation)
-         (progn
-           (while (looking-at citation)
-             (forward-line)
-             (move-to-column depth))
-           (let ((overlay (make-overlay beg-sub (point)))
-                  (invis-spec (make-symbol "notmuch-citation-region")))
-              (add-to-invisibility-spec invis-spec)
-             (overlay-put overlay 'invisible invis-spec)
-              (let ((p (point-marker))
-                    (cite-button-text
-                     (concat "["  (number-to-string (count-lines beg-sub (point)))
-                             "-line citation. Click/Enter to show.]")))
-                (goto-char (- beg-sub 1))
-                (insert (concat "\n" indent))
-                (insert-button cite-button-text
-                               'invisibility-spec invis-spec
-                               :type 'notmuch-button-citation-toggle-type)
-                (forward-line)
-              ))))
-      (move-to-column depth)
-      (if (looking-at notmuch-show-signature-regexp)
-         (let ((sig-lines (- (count-lines beg-sub end) 1)))
-           (if (<= sig-lines notmuch-show-signature-lines-max)
-               (progn
-                  (let ((invis-spec (make-symbol "notmuch-signature-region")))
-                    (add-to-invisibility-spec invis-spec)
-                    (overlay-put (make-overlay beg-sub end)
-                                 'invisible invis-spec)
-                  
-                    (goto-char (- beg-sub 1))
-                    (insert (concat "\n" indent))
-                    (let ((sig-button-text (concat "[" (number-to-string sig-lines)
-                                                   "-line signature. Click/Enter to show.]")))
-                      (insert-button sig-button-text 'invisibility-spec invis-spec
-                                     :type 'notmuch-button-signature-toggle-type)
-                     )
-                    (insert "\n")
-                    (goto-char end))))))
-      (forward-line))))
+  "Markup citations, and up to one signature in the given region"
+  ;; it would be nice if the untabify was not required, but
+  ;; that would require notmuch to indent with spaces.
+  (untabify beg end)
+  (let ((citation-regexp (notmuch-show-citation-regexp depth))
+       (signature-regexp (concat (format "^[[:space:]]\\{%d\\}" depth)
+                                 notmuch-show-signature-regexp))
+       (indent (concat "\n" (make-string depth ? ))))
+    (goto-char beg)
+    (beginning-of-line)
+    (while (and (< (point) end)
+               (re-search-forward citation-regexp end t))
+      (let* ((cite-start (match-beginning 0))
+            (cite-end  (match-end 0))
+            (cite-lines (count-lines cite-start cite-end)))
+       (when (> cite-lines (1+ notmuch-show-citation-lines-prefix))
+         (goto-char cite-start)
+         (forward-line notmuch-show-citation-lines-prefix)
+         (notmuch-show-region-to-button
+          (point) cite-end
+          "citation"
+          indent
+          (format notmuch-show-citation-button-format
+                  (- cite-lines notmuch-show-citation-lines-prefix))
+          ))))
+    (if (and (< (point) end)
+            (re-search-forward signature-regexp end t))
+       (let* ((sig-start (match-beginning 0))
+              (sig-end (match-end 0))
+              (sig-lines (1- (count-lines sig-start end))))
+         (if (<= sig-lines notmuch-show-signature-lines-max)
+             (notmuch-show-region-to-button
+              sig-start
+              end
+              "signature"
+              indent
+              (format notmuch-show-signature-button-format sig-lines)
+              ))))))
 
 (defun notmuch-show-markup-part (beg end depth)
   (if (re-search-forward notmuch-show-part-begin-regexp nil t)
       (progn
 
 (defun notmuch-show-markup-part (beg end depth)
   (if (re-search-forward notmuch-show-part-begin-regexp nil t)
       (progn
-       (forward-line)
-       (let ((beg (point-marker)))
-         (re-search-forward notmuch-show-part-end-regexp)
-         (let ((end (copy-marker (match-beginning 0))))
-           (goto-char end)
-           (if (not (bolp))
-               (insert "\n"))
-           (indent-rigidly beg end depth)
-           (notmuch-show-markup-citations-region beg end depth)
-           ; Advance to the next part (if any) (so the outer loop can
-           ; determine whether we've left the current message.
-           (if (re-search-forward notmuch-show-part-begin-regexp nil t)
-               (beginning-of-line)))))
+        (let (mime-message mime-type)
+          (save-excursion
+            (re-search-forward notmuch-show-contentype-regexp end t)
+            (setq mime-type (car (split-string (buffer-substring 
+                                                (match-beginning 1) (match-end 1))))))
+
+          (if (equal mime-type "text/html")
+              (let ((filename (notmuch-show-get-filename)))
+                (with-temp-buffer
+                  (insert-file-contents filename nil nil nil t)
+                  (setq mime-message (mm-dissect-buffer)))))
+          (forward-line)
+          (let ((beg (point-marker)))
+            (re-search-forward notmuch-show-part-end-regexp)
+            (let ((end (copy-marker (match-beginning 0))))
+              (goto-char end)
+              (if (not (bolp))
+                  (insert "\n"))
+              (indent-rigidly beg end depth)
+              (if (not (eq mime-message nil))
+                  (save-excursion
+                    (goto-char beg)
+                    (forward-line -1)
+                    (let ((handle-type (mm-handle-type mime-message))
+                          mime-type)
+                      (if (sequencep (car handle-type))
+                          (setq mime-type (car handle-type))
+                        (setq mime-type (car (car (cdr handle-type))))
+                        )
+                      (if (equal mime-type "text/html")
+                          (mm-display-part mime-message))))
+                )
+              (notmuch-show-markup-citations-region beg end depth)
+              ; Advance to the next part (if any) (so the outer loop can
+              ; determine whether we've left the current message.
+              (if (re-search-forward notmuch-show-part-begin-regexp nil t)
+                  (beginning-of-line)))))
+        (goto-char end))
     (goto-char end)))
 
 (defun notmuch-show-markup-parts-region (beg end depth)
     (goto-char end)))
 
 (defun notmuch-show-markup-parts-region (beg end depth)
@@ -961,7 +1048,7 @@ The optional PARENT-BUFFER is the notmuch-search buffer from
 which this notmuch-show command was executed, (so that the next
 thread from that buffer can be show when done with this one).
 
 which this notmuch-show command was executed, (so that the next
 thread from that buffer can be show when done with this one).
 
-The optional QUERY-CONTEXT is a notmuch search term. Only messages from the thread 
+The optional QUERY-CONTEXT is a notmuch search term. Only messages from the thread
 matching this search term are shown if non-nil. "
   (interactive "sNotmuch show: ")
   (let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*"))))
 matching this search term are shown if non-nil. "
   (interactive "sNotmuch show: ")
   (let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*"))))
@@ -1401,12 +1488,15 @@ current search results AND that are tagged with the given tag."
     (define-key map "?" 'notmuch-help)
     (define-key map "x" 'kill-this-buffer)
     (define-key map "q" 'kill-this-buffer)
     (define-key map "?" 'notmuch-help)
     (define-key map "x" 'kill-this-buffer)
     (define-key map "q" 'kill-this-buffer)
+    (define-key map "m" 'message-mail)
+    (define-key map "e" 'notmuch-folder-show-empty-toggle)
     (define-key map ">" 'notmuch-folder-last)
     (define-key map "<" 'notmuch-folder-first)
     (define-key map "=" 'notmuch-folder)
     (define-key map "s" 'notmuch-search)
     (define-key map [mouse-1] 'notmuch-folder-show-search)
     (define-key map (kbd "RET") 'notmuch-folder-show-search)
     (define-key map ">" 'notmuch-folder-last)
     (define-key map "<" 'notmuch-folder-first)
     (define-key map "=" 'notmuch-folder)
     (define-key map "s" 'notmuch-search)
     (define-key map [mouse-1] 'notmuch-folder-show-search)
     (define-key map (kbd "RET") 'notmuch-folder-show-search)
+    (define-key map " " 'notmuch-folder-show-search)
     (define-key map "p" 'notmuch-folder-previous)
     (define-key map "n" 'notmuch-folder-next)
     map)
     (define-key map "p" 'notmuch-folder-previous)
     (define-key map "n" 'notmuch-folder-next)
     map)
@@ -1476,22 +1566,40 @@ Currently available key bindings:
   (goto-char (point-max))
   (forward-line -1))
 
   (goto-char (point-max))
   (forward-line -1))
 
+(defun notmuch-folder-count (search)
+  (car (process-lines notmuch-command "count" search)))
+
+(setq notmuch-folder-show-empty t)
+
+(defun notmuch-folder-show-empty-toggle ()
+  "Toggle the listing of empty folders"
+  (interactive)
+  (setq notmuch-folder-show-empty (not notmuch-folder-show-empty))
+  (notmuch-folder))
+
 (defun notmuch-folder-add (folders)
   (if folders
 (defun notmuch-folder-add (folders)
   (if folders
-      (let ((name (car (car folders)))
+      (let* ((name (car (car folders)))
            (inhibit-read-only t)
            (inhibit-read-only t)
-           (search (cdr (car folders))))
-       (insert name)
-       (indent-to 16 1)
-       (call-process notmuch-command nil t nil "count" search)
+           (search (cdr (car folders)))
+           (count (notmuch-folder-count search)))
+       (if (or notmuch-folder-show-empty
+               (not (equal count "0")))
+           (progn
+             (insert name)
+             (indent-to 16 1)
+             (insert count)
+             (insert "\n")
+             )
+         )
        (notmuch-folder-add (cdr folders)))))
 
 (defun notmuch-folder-find-name ()
   (save-excursion
     (beginning-of-line)
     (let ((beg (point)))
        (notmuch-folder-add (cdr folders)))))
 
 (defun notmuch-folder-find-name ()
   (save-excursion
     (beginning-of-line)
     (let ((beg (point)))
-      (forward-word)
-      (filter-buffer-substring beg (point)))))
+      (re-search-forward "\\([ \t]*[^ \t]+\\)")
+      (filter-buffer-substring (match-beginning 1) (match-end 1)))))
 
 (defun notmuch-folder-show-search (&optional folder)
   "Show a search window for the search related to the specified folder."
 
 (defun notmuch-folder-show-search (&optional folder)
   "Show a search window for the search related to the specified folder."