]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.el
buttonize signatures as well
[notmuch] / notmuch.el
index 0eac1ccbed60efa586cdea6eafa223b2373127ac..93f3914fb7ded43b8913a6b7cfcae036e7778102 100644 (file)
 ;
 ; Authors: Carl Worth <cworth@cworth.org>
 
-(load "cl-seq")
+; This is an emacs-based interface to the notmuch mail system.
+;
+; You will first need to have the notmuch program installed and have a
+; notmuch database built in order to use this. See
+; http://notmuchmail.org for details.
+;
+; To install this software, copy it to a directory that is on the
+; `load-path' variable within emacs (a good candidate is
+; /usr/local/share/emacs/site-lisp). If you are viewing this from the
+; notmuch source distribution then you can simply run:
+;
+;      sudo make install-emacs
+;
+; to install it.
+;
+; Then, to actually run it, add:
+;
+;      (require 'notmuch)
+;
+; to your ~/.emacs file, and then run "M-x notmuch" from within emacs,
+; or run:
+;
+;      emacs -f notmuch
+;
+; Have fun, and let us know if you have any comment, questions, or
+; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
+; required, but is available from http://notmuchmail.org).
+
+(require 'cl)
+(require 'mm-view)
 
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
     (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 "m" 'message-mail)
     (define-key map "n" 'notmuch-show-next-message)
     (define-key map "N" 'notmuch-show-mark-read-then-next-open-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 "r" 'notmuch-show-reply)
     (define-key map "s" 'notmuch-show-toggle-signatures-visible)
+    (define-key map "v" 'notmuch-show-view-all-mime-parts)
     (define-key map "w" 'notmuch-show-view-raw-message)
     (define-key map "x" 'kill-this-buffer)
     (define-key map "+" 'notmuch-show-add-tag)
     (define-key map (kbd "DEL") 'notmuch-show-rewind)
     (define-key map " " 'notmuch-show-advance-marking-read-and-archiving)
     (define-key map "|" 'notmuch-show-pipe-message)
+    (define-key map "?" 'describe-mode)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
 
-(defvar notmuch-show-signature-lines-max 6
+(defvar notmuch-show-signature-regexp "\\(-- ?\\|_+\\)$"
+  "Pattern to match a line that separates content from signature.
+
+The regexp can (and should) include $ to match the end of the
+line, but should not include ^ to match the beginning of the
+line. This is because notmuch may have inserted additional space
+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-lines-max 12
   "Maximum length of signature that will be hidden by default.")
 
-(set 'notmuch-show-message-begin-regexp    "\fmessage{")
-(set 'notmuch-show-message-end-regexp      "\fmessage}")
-(set 'notmuch-show-header-begin-regexp     "\fheader{")
-(set 'notmuch-show-header-end-regexp       "\fheader}")
-(set 'notmuch-show-body-begin-regexp       "\fbody{")
-(set 'notmuch-show-body-end-regexp         "\fbody}")
-(set 'notmuch-show-attachment-begin-regexp "\fattachment{")
-(set 'notmuch-show-attachment-end-regexp   "\fattachment}")
-(set 'notmuch-show-part-begin-regexp       "\fpart{")
-(set 'notmuch-show-part-end-regexp         "\fpart}")
-(set 'notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$")
-
-(set 'notmuch-show-id-regexp "id:\\([^ ]*\\)")
-(set 'notmuch-show-filename-regexp "filename:\\(.*\\)$")
-(set 'notmuch-show-tags-regexp "(\\([^)]*\\))$")
+(defvar notmuch-command "notmuch"
+  "Command to run the notmuch binary.")
+
+(defvar notmuch-show-message-begin-regexp    "\fmessage{")
+(defvar notmuch-show-message-end-regexp      "\fmessage}")
+(defvar notmuch-show-header-begin-regexp     "\fheader{")
+(defvar notmuch-show-header-end-regexp       "\fheader}")
+(defvar notmuch-show-body-begin-regexp       "\fbody{")
+(defvar notmuch-show-body-end-regexp         "\fbody}")
+(defvar notmuch-show-attachment-begin-regexp "\fattachment{")
+(defvar notmuch-show-attachment-end-regexp   "\fattachment}")
+(defvar notmuch-show-part-begin-regexp       "\fpart{")
+(defvar notmuch-show-part-end-regexp         "\fpart}")
+(defvar notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$")
+
+(defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)")
+(defvar notmuch-show-depth-regexp " depth:\\([0-9]*\\) ")
+(defvar notmuch-show-filename-regexp "filename:\\(.*\\)$")
+(defvar notmuch-show-tags-regexp "(\\([^)]*\\))$")
+
+(defvar notmuch-show-parent-buffer nil)
+(defvar notmuch-show-body-read-visible nil)
+(defvar notmuch-show-citations-visible nil)
+(defvar notmuch-show-signatures-visible nil)
+(defvar notmuch-show-headers-visible nil)
 
 ; XXX: This should be a generic function in emacs somewhere, not here
 (defun point-invisible-p ()
@@ -153,7 +206,7 @@ Unlike builtin `next-line' this version accepts no arguments."
   (apply 'notmuch-call-notmuch-process
         (append (cons "tag"
                       (mapcar (lambda (s) (concat "+" s)) toadd))
-                (cons (concat "id:" (notmuch-show-get-message-id)) nil)))
+                (cons (notmuch-show-get-message-id) nil)))
   (notmuch-show-set-tags (sort (union toadd (notmuch-show-get-tags) :test 'string=) 'string<)))
 
 (defun notmuch-show-remove-tag (&rest toremove)
@@ -165,7 +218,7 @@ Unlike builtin `next-line' this version accepts no arguments."
          (apply 'notmuch-call-notmuch-process
                 (append (cons "tag"
                               (mapcar (lambda (s) (concat "-" s)) toremove))
-                        (cons (concat "id:" (notmuch-show-get-message-id)) nil)))
+                        (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)
@@ -184,12 +237,13 @@ Unlike builtin `next-line' this version accepts no arguments."
     (if parent-buffer
        (progn
          (switch-to-buffer parent-buffer)
+         (forward-line)
          (notmuch-search-show-thread)))))
 
 (defun notmuch-show-mark-read-then-archive-thread ()
   "Remove \"unread\" tag from each message, then archive and show next thread.
 
-Archive each message currrently shown by removing the \"unread\"
+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.
@@ -204,7 +258,7 @@ buffer."
 (defun notmuch-show-archive-thread ()
   "Archive each message in thread, and show next thread from search.
 
-Archive each message currrently shown by removing the \"inbox\"
+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.
 
@@ -220,6 +274,34 @@ buffer."
   (interactive)
   (view-file (notmuch-show-get-filename)))
 
+(defun notmuch-show-view-all-mime-parts ()
+  "Use external viewers (according to mailcap) to view all MIME-encoded parts."
+  (interactive)
+  (save-excursion
+    (let ((filename (notmuch-show-get-filename)))
+      (switch-to-buffer (generate-new-buffer (concat "*notmuch-mime-"
+                                                    filename
+                                                    "*")))
+      (insert-file-contents filename nil nil nil t)
+      (mm-display-parts (mm-dissect-buffer))
+      (kill-this-buffer))))
+
+(defun notmuch-reply (query-string)
+  (switch-to-buffer (generate-new-buffer "notmuch-draft"))
+  (call-process notmuch-command nil t nil "reply" query-string)
+  (goto-char (point-min))
+  (if (re-search-forward "^$" nil t)
+      (progn
+       (insert "--text follows this line--")
+       (forward-line)))
+  (message-mode))
+
+(defun notmuch-show-reply ()
+  "Begin composing a reply to the current message in a new buffer."
+  (interactive)
+  (let ((message-id (notmuch-show-get-message-id)))
+    (notmuch-reply message-id)))
+
 (defun notmuch-show-pipe-message (command)
   "Pipe the contents of the current message to the given command.
 
@@ -299,7 +381,7 @@ there are no more unread messages past the current point."
       (notmuch-show-next-message)))
 
 (defun notmuch-show-next-open-message ()
-  "Advance to the the next message which is not hidden.
+  "Advance to the next message which is not hidden.
 
 If read messages are currently hidden, advance to the next unread
 message. Otherwise, advance to the next message."
@@ -395,59 +477,110 @@ which this thread was originally shown."
        (if last
            (notmuch-show-archive-thread))))))
 
-(defun notmuch-show-markup-citations-region (beg end)
+(defun notmuch-show-markup-citations-region (beg end depth)
   (goto-char beg)
   (beginning-of-line)
   (while (< (point) end)
-    (let ((beg-sub (point)))
-      (if (looking-at ">")
+    (let ((beg-sub (point-marker))
+         (indent (make-string depth ? ))
+         (citation "[[:space:]]*>"))
+      (if (looking-at citation)
          (progn
-           (while (looking-at ">")
+           (while (looking-at citation)
              (forward-line))
            (let ((overlay (make-overlay beg-sub (point))))
              (overlay-put overlay 'invisible 'notmuch-show-citation)
-             (overlay-put overlay 'before-string
-                          (concat "[" (number-to-string (count-lines beg-sub (point)))
-                                  " quoted lines.]\n")))))
-      (if (looking-at "--[ ]?$")
-         (let ((sig-lines (count-lines beg-sub end)))
+              (let (
+                    (p (point))
+                    (cite-button-text (concat "[" (number-to-string (count-lines beg-sub (point)))
+                                              "-line citation. Press 'c' to show.]"))
+                    )
+                (goto-char (- beg-sub 1))
+                (insert (concat "\n" indent))
+                (insert-button cite-button-text)
+                (insert "\n")
+                (goto-char (+ (length cite-button-text) p))
+              ))))
+      (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
-                 (overlay-put (make-overlay beg-sub (+ beg-sub 1))
-                              'before-string
-                              (concat "[" (number-to-string sig-lines)
-                                      "-line signature.]"))
-                 (overlay-put (make-overlay (+ beg-sub 2) end)
+                 (overlay-put (make-overlay beg-sub end)
                               'invisible 'notmuch-show-signature)
+                  (goto-char (- beg-sub 1))
+                  (insert (concat "\n" indent))
+                  (insert-button (concat "[" (number-to-string sig-lines)
+                                         "-line signature. Press 's' to show.]"))
+                  (insert "\n")
                  (goto-char end)))))
       (forward-line))))
 
-(defun notmuch-show-markup-body ()
+(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)))))
+    (goto-char end)))
+
+(defun notmuch-show-markup-parts-region (beg end depth)
+  (save-excursion
+    (goto-char beg)
+    (while (< (point) end)
+      (notmuch-show-markup-part beg end depth))))
+
+(defun notmuch-show-markup-body (depth)
   (re-search-forward notmuch-show-body-begin-regexp)
-  (next-line 1)
-  (beginning-of-line)
-  (let ((beg (point)))
+  (forward-line)
+  (let ((beg (point-marker)))
     (re-search-forward notmuch-show-body-end-regexp)
-    (let ((end (match-beginning 0)))
-      (notmuch-show-markup-citations-region beg end)
+    (let ((end (copy-marker (match-beginning 0))))
+      (notmuch-show-markup-parts-region beg end depth)
       (if (not (notmuch-show-message-unread-p))
          (overlay-put (make-overlay beg end)
-                      'invisible 'notmuch-show-body-read)))))
+                      'invisible 'notmuch-show-body-read))
+      (set-marker beg nil)
+      (set-marker end nil)
+      )))
 
-(defun notmuch-show-markup-header ()
+(defun notmuch-show-markup-header (depth)
   (re-search-forward notmuch-show-header-begin-regexp)
-  (next-line 2)
-  (beginning-of-line)
-  (let ((beg (point)))
-    (re-search-forward notmuch-show-header-end-regexp)
-    (overlay-put (make-overlay beg (match-beginning 0))
-                'invisible 'notmuch-show-header)))
+  (forward-line)
+  (let ((beg (point-marker)))
+    (end-of-line)
+    ; Inverse video for subject
+    (overlay-put (make-overlay beg (point)) 'face '(:inverse-video t))
+    (forward-line 2)
+    (let ((beg-hidden (point-marker)))
+      (re-search-forward notmuch-show-header-end-regexp)
+      (beginning-of-line)
+      (let ((end (point-marker)))
+       (indent-rigidly beg end depth)
+       (overlay-put (make-overlay beg-hidden end)
+                    'invisible 'notmuch-show-header)
+       (set-marker beg nil)
+       (set-marker beg-hidden nil)
+       (set-marker end nil)
+       ))))
 
 (defun notmuch-show-markup-message ()
   (if (re-search-forward notmuch-show-message-begin-regexp nil t)
       (progn
-       (notmuch-show-markup-header)
-       (notmuch-show-markup-body))
+       (re-search-forward notmuch-show-depth-regexp)
+       (let ((depth (string-to-number (buffer-substring (match-beginning 1) (match-end 1)))))
+         (notmuch-show-markup-header depth)
+         (notmuch-show-markup-body depth)))
     (goto-char (point-max))))
 
 (defun notmuch-show-hide-markers ()
@@ -553,6 +686,35 @@ view, (remove the \"inbox\" tag from each), with
        mode-name "notmuch-show")
   (setq buffer-read-only t))
 
+;;;###autoload
+
+(defgroup notmuch nil
+  "Notmuch mail reader for Emacs."
+  :group 'mail)
+
+(defcustom notmuch-show-hook nil
+  "List of functions to call when notmuch displays a message."
+  :type 'hook
+  :options '(goto-address)
+  :group 'notmuch)
+
+(defcustom notmuch-search-hook nil
+  "List of functions to call when notmuch displays the search results."
+  :type 'hook
+  :options '(hl-line-mode)
+  :group 'notmuch)
+
+; Make show mode a bit prettier, highlighting URLs and using word wrap
+
+(defun notmuch-show-pretty-hook ()
+  (goto-address-mode 1)
+  (visual-line-mode))
+
+(add-hook 'notmuch-show-hook 'notmuch-show-pretty-hook)
+(add-hook 'notmuch-search-hook
+         (lambda()
+           (hl-line-mode 1) ))
+
 (defun notmuch-show (thread-id &optional parent-buffer)
   "Run \"notmuch show\" with the given thread ID and display results.
 
@@ -567,14 +729,15 @@ thread from that buffer can be show when done with this one)."
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
       (if proc
-         (error "notmuch search process already running for query `%s'" query)
+         (error "notmuch search process already running for query `%s'" thread-id)
        )
       (erase-buffer)
       (goto-char (point-min))
       (save-excursion
-       (call-process "notmuch" nil t nil "show" thread-id)
+       (call-process notmuch-command nil t nil "show" thread-id)
        (notmuch-show-markup-messages)
        )
+      (run-hooks 'notmuch-show-hook)
       ; Move straight to the first unread message
       (if (not (notmuch-show-message-unread-p))
          (progn
@@ -588,14 +751,20 @@ thread from that buffer can be show when done with this one)."
                  (notmuch-show-toggle-body-read-visible)))))
       )))
 
+(defvar notmuch-search-authors-width 40
+  "Number of columns to use to display authors in a notmuch-search buffer.")
+
 (defvar notmuch-search-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map "a" 'notmuch-search-archive-thread)
     (define-key map "b" 'notmuch-search-scroll-down)
     (define-key map "f" 'notmuch-search-filter)
+    (define-key map "m" 'message-mail)
     (define-key map "n" 'next-line)
+    (define-key map "o" 'notmuch-search-toggle-order)
     (define-key map "p" 'previous-line)
     (define-key map "q" 'kill-this-buffer)
+    (define-key map "r" 'notmuch-search-reply-to-thread)
     (define-key map "s" 'notmuch-search)
     (define-key map "t" 'notmuch-search-filter-by-tag)
     (define-key map "x" 'kill-this-buffer)
@@ -608,10 +777,14 @@ thread from that buffer can be show when done with this one)."
     (define-key map "\M->" 'notmuch-search-goto-last-thread)
     (define-key map " " 'notmuch-search-scroll-up)
     (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
+    (define-key map "?" 'describe-mode)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
 
+(defvar notmuch-search-query-string)
+(defvar notmuch-search-oldest-first)
+
 (defun notmuch-search-scroll-up ()
   "Scroll up, moving point to last message in thread if at end."
   (interactive)
@@ -634,10 +807,10 @@ thread from that buffer can be show when done with this one)."
       (goto-char (window-start))
     (scroll-down nil)))
 
-(defun notmuch-search-goto-last-thread (&optional arg)
+(defun notmuch-search-goto-last-thread ()
   "Move point to the last thread in the buffer."
-  (interactive "^P")
-  (end-of-buffer arg)
+  (interactive)
+  (goto-char (point-max))
   (forward-line -1))
 
 ;;;###autoload
@@ -663,9 +836,11 @@ global search.
   (interactive)
   (kill-all-local-variables)
   (make-local-variable 'notmuch-search-query-string)
+  (make-local-variable 'notmuch-search-oldest-first)
   (set (make-local-variable 'scroll-preserve-screen-position) t)
   (add-to-invisibility-spec 'notmuch-search)
   (use-local-map notmuch-search-mode-map)
+  (setq truncate-lines t)
   (setq major-mode 'notmuch-search-mode
        mode-name "notmuch-search")
   (setq buffer-read-only t))
@@ -674,43 +849,47 @@ global search.
   (save-excursion
     (beginning-of-line)
     (let ((beg (point)))
-      (re-search-forward "thread:[a-fA-F0-9]*")
+      (re-search-forward "thread:[a-fA-F0-9]*" nil t)
       (filter-buffer-substring beg (point)))))
 
 (defun notmuch-search-markup-this-thread-id ()
   (beginning-of-line)
   (let ((beg (point)))
-    (re-search-forward "thread:[a-fA-F0-9]*")
-    (forward-char)
-    (overlay-put (make-overlay beg (point)) 'invisible 'notmuch-search)))
+    (if (re-search-forward "thread:[a-fA-F0-9]*" nil t)
+       (progn
+         (forward-char)
+         (overlay-put (make-overlay beg (point)) 'invisible 'notmuch-search)
+         (re-search-forward ".*\\[[0-9]*/[0-9]*\\] \\([^;]*\\)\\(;\\)")
+         (let* ((authors (buffer-substring (match-beginning 1) (match-end 1)))
+                (authors-length (length authors)))
+           ;; Drop the semi-colon
+           (replace-match "" t nil nil 2)
+           (if (<= authors-length notmuch-search-authors-width)
+               (replace-match (concat authors (make-string
+                                               (- notmuch-search-authors-width
+                                                  authors-length) ? )) t t nil 1)
+             (replace-match (concat (substring authors 0 (- notmuch-search-authors-width 3)) "...") t t nil 1)))))))
 
 (defun notmuch-search-markup-thread-ids ()
   (save-excursion
     (goto-char (point-min))
     (while (not (eobp))
       (notmuch-search-markup-this-thread-id)
-      (next-line))))
-
-(defun notmuch-search-hide-thread-ids ()
-  (interactive)
-  (add-to-invisibility-spec 'notmuch-search)
-  (force-window-update)
-  (redisplay t))
-
-(defun notmuch-search-show-thread-ids ()
-  (interactive)
-  (remove-from-invisibility-spec 'notmuch-search)
-  (force-window-update)
-  (redisplay t))
+      (forward-line))))
 
 (defun notmuch-search-show-thread ()
   (interactive)
   (let ((thread-id (notmuch-search-find-thread-id)))
-    (forward-line)
     (if (> (length thread-id) 0)
        (notmuch-show thread-id (current-buffer))
       (error "End of search results"))))
 
+(defun notmuch-search-reply-to-thread ()
+  "Begin composing a reply to the entire current thread in a new buffer."
+  (interactive)
+  (let ((message-id (notmuch-search-find-thread-id)))
+    (notmuch-reply message-id)))
+
 (defun notmuch-call-notmuch-process (&rest args)
   "Synchronously invoke \"notmuch\" with the given list of arguments.
 
@@ -719,7 +898,7 @@ and will also appear in a buffer named \"*Notmuch errors*\"."
   (let ((error-buffer (get-buffer-create "*Notmuch errors*")))
     (with-current-buffer error-buffer
        (erase-buffer))
-    (if (eq (apply 'call-process "notmuch" nil error-buffer nil args) 0)
+    (if (eq (apply 'call-process notmuch-command nil error-buffer nil args) 0)
        (point)
       (progn
        (with-current-buffer error-buffer
@@ -752,12 +931,12 @@ and will also appear in a buffer named \"*Notmuch errors*\"."
 
 (defun notmuch-search-add-tag (tag)
   (interactive "sTag to add: ")
-  (notmuch-call-notmuch-process "tag" (concat "+" tag) (concat "thread:" (notmuch-search-find-thread-id)))
+  (notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id))
   (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<))))
 
 (defun notmuch-search-remove-tag (tag)
   (interactive "sTag to remove: ")
-  (notmuch-call-notmuch-process "tag" (concat "-" tag) (concat "thread:" (notmuch-search-find-thread-id)))
+  (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id))
   (notmuch-search-set-tags (delete tag (notmuch-search-get-tags))))
 
 (defun notmuch-search-archive-thread ()
@@ -768,13 +947,14 @@ This function advances point to the next line when finished."
   (notmuch-search-remove-tag "inbox")
   (forward-line))
 
-(defun notmuch-search (query)
+(defun notmuch-search (query &optional oldest-first)
   "Run \"notmuch search\" with the given query string and display results."
   (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)
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
       (if proc
@@ -783,9 +963,12 @@ This function advances point to the next line when finished."
       (erase-buffer)
       (goto-char (point-min))
       (save-excursion
-       (call-process "notmuch" nil t nil "search" query)
+       (if oldest-first
+           (call-process notmuch-command nil t nil "search" "--sort=oldest-first" query)
+         (call-process notmuch-command nil t nil "search" "--sort=newest-first" query))
        (notmuch-search-markup-thread-ids)
-       ))))
+       ))
+    (run-hooks 'notmuch-search-hook)))
 
 (defun notmuch-search-refresh-view ()
   "Refresh the current view.
@@ -797,22 +980,41 @@ thread. Otherwise, point will be moved to attempt to be in the
 same relative position within the new buffer."
   (interactive)
   (let ((here (point))
+       (oldest-first notmuch-search-oldest-first)
        (thread (notmuch-search-find-thread-id))
        (query notmuch-search-query-string))
     (kill-this-buffer)
-    (notmuch-search query)
+    (notmuch-search query oldest-first)
     (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.
+
+By default, the \"inbox\" view created by `notmuch' is displayed
+in chronological order (oldest thread at the beginning of the
+buffer), while any global searches created by `notmuch-search'
+are displayed in reverse-chronological order (newest thread at
+the beginning of the buffer).
+
+This command toggles the sort order for the current search.
+
+Note that any filtered searches created by
+`notmuch-search-filter' retain the search order of the parent
+search."
+  (interactive)
+  (set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
+  (notmuch-search-refresh-view))
+
 (defun notmuch-search-filter (query)
   "Filter the current search results based on an additional query string.
 
 Runs a new search matching only messages that match both the
 current search results AND the additional query string provided."
   (interactive "sFilter search: ")
-  (notmuch-search (concat notmuch-search-query-string " and " query)))
+  (notmuch-search (concat notmuch-search-query-string " and " query) notmuch-search-oldest-first))
 
 (defun notmuch-search-filter-by-tag (tag)
   "Filter the current search results based on a single tag.
@@ -820,11 +1022,13 @@ current search results AND the additional query string provided."
 Runs a new search matching only messages that match both the
 current search results AND that are tagged with the given tag."
   (interactive "sFilter by tag: ")
-  (notmuch-search (concat notmuch-search-query-string " and tag:" tag)))
+  (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first))
 
 (defun notmuch ()
   "Run notmuch to display all mail with tag of 'inbox'"
   (interactive)
-  (notmuch-search "tag:inbox"))
+  (notmuch-search "tag:inbox" t))
+
+(setq mail-user-agent 'message-user-agent)
 
 (provide 'notmuch)