]> 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).")
 
+(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-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.")
 
@@ -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-contentype-regexp "Content-type: \\(.*\\)")
+
 (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)
-        (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
-   (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))
@@ -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)
-  (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)
-  (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)))
@@ -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
-  '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"
@@ -616,71 +651,123 @@ which this thread was originally shown."
   '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)
-  (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
-       (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)
@@ -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).
 
-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 "*"))))
@@ -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 "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-show-search)
     (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))
 
+(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
-      (let ((name (car (car folders)))
+      (let* ((name (car (car folders)))
            (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)))
-      (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."