(require 'notmuch-lib)
(require 'notmuch-show)
+(require 'notmuch-mua)
+
+(defcustom notmuch-search-result-format
+ `(("date" . "%s ")
+ ("count" . "%-7s ")
+ ("authors" . "%-20s ")
+ ("subject" . "%s ")
+ ("tags" . "(%s)"))
+ "Search result formating. Supported fields are:
+ date, count, authors, subject, tags
+For example:
+ (setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\)
+ \(\"subject\" . \"%s\"\)\)\)"
+ :type '(alist :key-type (string) :value-type (string))
+ :group 'notmuch)
(defun notmuch-select-tag-with-completion (prompt &rest search-terms)
(let ((tag-list
(mm-save-part p))))
mm-handle))
-(defun notmuch-reply (query-string)
- (switch-to-buffer (generate-new-buffer "notmuch-draft"))
- (call-process notmuch-command nil t nil "reply" query-string)
- (message-insert-signature)
- (goto-char (point-min))
- (if (re-search-forward "^$" nil t)
- (progn
- (insert "--text follows this line--")
- (forward-line)))
- (message-mode))
-
-(defun notmuch-toggle-invisible-action (cite-button)
- (let ((invis-spec (button-get cite-button 'invisibility-spec)))
- (if (invisible-p invis-spec)
- (remove-from-invisibility-spec invis-spec)
- (add-to-invisibility-spec invis-spec)
- ))
- (force-window-update)
- (redisplay t))
-
-(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"
- :supertype 'notmuch-button-invisibility-toggle-type)
-(define-button-type 'notmuch-button-body-toggle-type
- 'help-echo "mouse-1, RET: Show message"
- 'face 'notmuch-message-summary-face
- :supertype 'notmuch-button-invisibility-toggle-type)
-
-(defun notmuch-fontify-headers ()
- (while (looking-at "[[:space:]]")
- (forward-char))
- (if (looking-at "[Tt]o:")
- (progn
- (overlay-put (make-overlay (point) (re-search-forward ":"))
- 'face 'message-header-name)
- (overlay-put (make-overlay (point) (re-search-forward ".*$"))
- 'face 'message-header-to))
- (if (looking-at "[B]?[Cc][Cc]:")
- (progn
- (overlay-put (make-overlay (point) (re-search-forward ":"))
- 'face 'message-header-name)
- (overlay-put (make-overlay (point) (re-search-forward ".*$"))
- 'face 'message-header-cc))
- (if (looking-at "[Ss]ubject:")
- (progn
- (overlay-put (make-overlay (point) (re-search-forward ":"))
- 'face 'message-header-name)
- (overlay-put (make-overlay (point) (re-search-forward ".*$"))
- 'face 'message-header-subject))
- (if (looking-at "[Ff]rom:")
- (progn
- (overlay-put (make-overlay (point) (re-search-forward ":"))
- 'face 'message-header-name)
- (overlay-put (make-overlay (point) (re-search-forward ".*$"))
- 'face 'message-header-other))
- (if (looking-at "[Dd]ate:")
- (progn
- (overlay-put (make-overlay (point) (re-search-forward ":"))
- 'face 'message-header-name)
- (overlay-put (make-overlay (point) (re-search-forward ".*$"))
- 'face 'message-header-other))))))))
-
(defun notmuch-documentation-first-line (symbol)
"Return the first line of the documentation string for SYMBOL."
(let ((doc (documentation symbol)))
(set-buffer-modified-p nil)
(view-buffer (current-buffer) 'kill-buffer-if-not-modified))))
-(defgroup notmuch nil
- "Notmuch mail reader for Emacs."
- :group 'mail)
-
-(defcustom notmuch-search-hook nil
+(defcustom notmuch-search-hook '(hl-line-mode)
"List of functions to call when notmuch displays the search results."
:type 'hook
:options '(hl-line-mode)
:group 'notmuch)
-(defvar notmuch-search-authors-width 20
- "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 "?" 'notmuch-help)
- (define-key map "q" 'kill-this-buffer)
- (define-key map "x" 'kill-this-buffer)
+ (define-key map "q" 'notmuch-search-quit)
+ (define-key map "x" 'notmuch-search-quit)
(define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
(define-key map "b" 'notmuch-search-scroll-down)
(define-key map " " 'notmuch-search-scroll-up)
(define-key map "p" 'notmuch-search-previous-thread)
(define-key map "n" 'notmuch-search-next-thread)
(define-key map "r" 'notmuch-search-reply-to-thread)
- (define-key map "m" 'message-mail)
+ (define-key map "m" 'notmuch-mua-mail)
(define-key map "s" 'notmuch-search)
(define-key map "o" 'notmuch-search-toggle-order)
(define-key map "=" 'notmuch-search-refresh-view)
+ (define-key map "G" 'notmuch-search-poll-and-refresh-view)
(define-key map "t" 'notmuch-search-filter-by-tag)
(define-key map "f" 'notmuch-search-filter)
(define-key map [mouse-1] 'notmuch-search-show-thread)
(define-key map "-" 'notmuch-search-remove-tag)
(define-key map "+" 'notmuch-search-add-tag)
(define-key map (kbd "RET") 'notmuch-search-show-thread)
- (define-key map "F" 'notmuch-folder)
map)
"Keymap for \"notmuch search\" buffers.")
(fset 'notmuch-search-mode-map notmuch-search-mode-map)
(defvar notmuch-search-target-line)
(defvar notmuch-search-oldest-first t
"Show the oldest mail first in the search-mode")
+(defvar notmuch-search-continuation)
(defvar notmuch-search-disjunctive-regexp "\\<[oO][rR]\\>")
+(defun notmuch-search-quit ()
+ "Exit the search buffer, calling any defined continuation function."
+ (interactive)
+ (let ((continuation notmuch-search-continuation))
+ (kill-this-buffer)
+ (when continuation
+ (funcall continuation))))
+
(defun notmuch-search-scroll-up ()
"Move forward through search results by one window's worth."
(interactive)
"Notmuch search mode face used to highligh tags."
:group 'notmuch)
-(defvar notmuch-tag-face-alist nil
- "List containing the tag list that need to be highlighed")
-
-(defvar notmuch-search-font-lock-keywords nil)
-
;;;###autoload
(defun notmuch-search-mode ()
"Major mode displaying results of a notmuch search.
(make-local-variable 'notmuch-search-oldest-first)
(make-local-variable 'notmuch-search-target-thread)
(make-local-variable 'notmuch-search-target-line)
+ (set (make-local-variable 'notmuch-search-continuation) nil)
(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)
- (if (not notmuch-tag-face-alist)
- (add-to-list 'notmuch-search-font-lock-keywords (list
- "(\\([^()]*\\))$" '(1 'notmuch-tag-face)))
- (let ((notmuch-search-tags (mapcar 'car notmuch-tag-face-alist)))
- (loop for notmuch-search-tag in notmuch-search-tags
- do (add-to-list 'notmuch-search-font-lock-keywords (list
- (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$")
- `(1 ,(cdr (assoc notmuch-search-tag notmuch-tag-face-alist))))))))
- (set (make-local-variable 'font-lock-defaults)
- '(notmuch-search-font-lock-keywords t)))
+ (setq buffer-read-only t))
(defun notmuch-search-properties-in-region (property beg end)
(save-excursion
"Display the currently selected thread."
(interactive)
(let ((thread-id (notmuch-search-find-thread-id))
- (subject (notmuch-search-find-subject))
- buffer-name)
- (when (string-match "^[ \t]*$" subject)
- (setq subject "[No Subject]"))
- (setq buffer-name (concat "*"
- (truncate-string-to-width subject 32 nil nil t)
- "*"))
+ (subject (notmuch-search-find-subject)))
(if (> (length thread-id) 0)
(notmuch-show thread-id
(current-buffer)
notmuch-search-query-string
- buffer-name)
+ ;; name the buffer based on notmuch-search-find-subject
+ (if (string-match "^[ \t]*$" subject)
+ "[No Subject]"
+ (truncate-string-to-width
+ (concat "*"
+ (truncate-string-to-width subject 32 nil nil t)
+ "*")
+ 32 nil nil t)))
(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)))
+ (notmuch-mua-reply message-id)))
(defun notmuch-call-notmuch-process (&rest args)
"Synchronously invoke \"notmuch\" with the given list of arguments.
(backward-char)
(let ((end (point)))
(delete-region beg end)
- (insert (mapconcat 'identity tags " "))))))
+ (insert (propertize (mapconcat 'identity tags " ")
+ 'font-lock-face 'notmuch-tag-face))))))
(defun notmuch-search-get-tags ()
(save-excursion
output)))
(defun notmuch-search-add-tag-thread (tag)
- (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<))))
+ (notmuch-search-add-tag-region tag (point) (point)))
(defun notmuch-search-add-tag-region (tag beg end)
(let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))
(forward-line))))))
(defun notmuch-search-remove-tag-thread (tag)
- (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id))
- (notmuch-search-set-tags (delete tag (notmuch-search-get-tags))))
+ (notmuch-search-remove-tag-region tag (point) (point)))
(defun notmuch-search-remove-tag-region (tag beg end)
(let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))
(t
(setq tags-faces (cdr tags-faces)))))))))
+(defun notmuch-search-insert-field (field date count authors subject tags)
+ (cond
+ ((string-equal field "date")
+ (insert (format (cdr (assoc field notmuch-search-result-format)) date)))
+ ((string-equal field "count")
+ (insert (format (cdr (assoc field notmuch-search-result-format)) count)))
+ ((string-equal field "authors")
+ (insert (let* ((format-string (cdr (assoc field notmuch-search-result-format)))
+ (formatted-sample (format format-string ""))
+ (formatted-authors (format format-string authors)))
+ (if (> (length formatted-authors)
+ (length formatted-sample))
+ (concat (substring authors 0 (- (length formatted-sample) 4)) "... ")
+ formatted-authors))))
+ ((string-equal field "subject")
+ (insert (format (cdr (assoc field notmuch-search-result-format)) subject)))
+ ((string-equal field "tags")
+ (insert (concat "(" (propertize tags 'font-lock-face 'notmuch-tag-face) ")")))))
+
+(defun notmuch-search-show-result (date count authors subject tags)
+ (let ((fields) (field))
+ (setq fields (mapcar 'car notmuch-search-result-format))
+ (loop for field in fields
+ do (notmuch-search-insert-field field date count authors subject tags)))
+ (insert "\n"))
+
(defun notmuch-search-process-filter (proc string)
"Process and filter the output of \"notmuch search\""
(let ((buffer (process-buffer proc))
(date (match-string 2 string))
(count (match-string 3 string))
(authors (match-string 4 string))
- (authors-length (length authors))
(subject (match-string 5 string))
(tags (match-string 6 string))
(tag-list (if tags (save-match-data (split-string tags)))))
- (if (> authors-length notmuch-search-authors-width)
- (set 'authors (concat (substring authors 0 (- notmuch-search-authors-width 3)) "...")))
(goto-char (point-max))
- (let ((beg (point-marker))
- (format-string (format "%%s %%-7s %%-%ds %%s (%%s)\n" notmuch-search-authors-width)))
- (insert (format format-string date count authors subject tags))
+ (let ((beg (point-marker)))
+ (notmuch-search-show-result date count authors subject tags)
(notmuch-search-color-line beg (point-marker) tag-list)
(put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id)
(put-text-property beg (point-marker) 'notmuch-search-authors authors)
(apply 'notmuch-call-notmuch-process "tag"
(append action-split (list notmuch-search-query-string) nil))))
+(defcustom notmuch-folders (quote (("inbox" . "tag:inbox") ("unread" . "tag:unread")))
+ "List of searches for the notmuch folder view"
+ :type '(alist :key-type (string) :value-type (string))
+ :group 'notmuch)
+
+(defun notmuch-search-buffer-title (query)
+ "Returns the title for a buffer with notmuch search results."
+ (let* ((folder (rassoc-if (lambda (key)
+ (string-match (concat "^" (regexp-quote key))
+ query))
+ notmuch-folders))
+ (folder-name (car folder))
+ (folder-query (cdr folder)))
+ (cond ((and folder (equal folder-query query))
+ ;; Query is the same as folder search (ignoring case)
+ (concat "*notmuch-folder-" folder-name "*"))
+ (folder
+ (concat "*notmuch-search-"
+ (replace-regexp-in-string (concat "^" (regexp-quote folder-query))
+ (concat "[ " folder-name " ]")
+ query)
+ "*"))
+ (t
+ (concat "*notmuch-search-" query "*"))
+ )))
+
;;;###autoload
-(defun notmuch-search (query &optional oldest-first target-thread target-line)
+(defun notmuch-search (query &optional oldest-first target-thread target-line continuation)
"Run \"notmuch search\" with the given query string and display results.
The optional parameters are used as follows:
target-line: The line number to move to if the target thread does not
appear in the search results."
(interactive "sNotmuch search: ")
- (let ((buffer (get-buffer-create (concat "*notmuch-search-" query "*"))))
+ (let ((buffer (get-buffer-create (notmuch-search-buffer-title 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-line target-line)
+ (set 'notmuch-search-continuation continuation)
(let ((proc (get-buffer-process (current-buffer)))
(inhibit-read-only t))
(if proc
(let ((target-line (line-number-at-pos))
(oldest-first notmuch-search-oldest-first)
(target-thread (notmuch-search-find-thread-id))
- (query notmuch-search-query-string))
+ (query notmuch-search-query-string)
+ (continuation notmuch-search-continuation))
(kill-this-buffer)
- (notmuch-search query oldest-first target-thread target-line)
- (goto-char (point-min))
- ))
+ (notmuch-search query oldest-first target-thread target-line continuation)
+ (goto-char (point-min))))
+
+(defcustom notmuch-poll-script ""
+ "An external script to incorporate new mail into the notmuch database.
+
+If this variable is non empty, then it should name a script to be
+invoked by `notmuch-search-poll-and-refresh-view' and
+`notmuch-folder-poll-and-refresh-view' (each have a default
+keybinding of 'G'). The script could do any of the following
+depending on the user's needs:
+
+1. Invoke a program to transfer mail to the local mail store
+2. Invoke \"notmuch new\" to incorporate the new mail
+3. Invoke one or more \"notmuch tag\" commands to classify the mail"
+ :type 'string
+ :group 'notmuch)
+
+(defun notmuch-poll ()
+ "Run external script to import mail.
+
+Invokes `notmuch-poll-script' if it is not set to an empty string."
+ (interactive)
+ (if (not (string= notmuch-poll-script ""))
+ (call-process notmuch-poll-script nil nil)))
+
+(defun notmuch-search-poll-and-refresh-view ()
+ "Invoke `notmuch-poll' to import mail, then refresh the current view."
+ (interactive)
+ (notmuch-poll)
+ (notmuch-search-refresh-view))
(defun notmuch-search-toggle-order ()
"Toggle the current search order.
Runs a new search matching only messages that match both the
current search results AND the additional query string provided."
(interactive "sFilter search: ")
- (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query) (concat "( " query " )") query)))
- (notmuch-search (concat notmuch-search-query-string " and " grouped-query) notmuch-search-oldest-first)))
+ (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query)
+ (concat "( " query " )")
+ query)))
+ (notmuch-search (if (string= notmuch-search-query-string "*")
+ grouped-query
+ (concat notmuch-search-query-string " and " grouped-query)) notmuch-search-oldest-first)))
(defun notmuch-search-filter-by-tag (tag)
"Filter the current search results based on a single tag.
(interactive)
(notmuch-search "tag:inbox" notmuch-search-oldest-first))
-(setq mail-user-agent 'message-user-agent)
+(setq mail-user-agent 'notmuch-user-agent)
(defvar notmuch-folder-mode-map
(let ((map (make-sparse-keymap)))
(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 "m" 'notmuch-mua-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 "G" 'notmuch-folder-poll-and-refresh-view)
(define-key map "s" 'notmuch-search)
(define-key map [mouse-1] 'notmuch-folder-show-search)
(define-key map (kbd "RET") 'notmuch-folder-show-search)
(fset 'notmuch-folder-mode-map notmuch-folder-mode-map)
-(defcustom notmuch-folders (quote (("inbox" . "tag:inbox") ("unread" . "tag:unread")))
- "List of searches for the notmuch folder view"
- :type '(alist :key-type (string) :value-type (string))
- :group 'notmuch)
-
(defun notmuch-folder-mode ()
"Major mode for showing notmuch 'folders'.
(if search
(notmuch-search (cdr search) notmuch-search-oldest-first))))
+(defun notmuch-folder-poll-and-refresh-view ()
+ "Invoke `notmuch-poll' to import mail, then refresh the folder view."
+ (interactive)
+ (notmuch-poll)
+ (notmuch-folder))
+
;;;###autoload
(defun notmuch-folder ()
"Show the notmuch folder view and update the displayed counts."