X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-lib.el;h=d78bcf8065aae699a162241e9e7e92b114a3267a;hp=900235bc5edbf59a03c2758da13906eac077e6a0;hb=401dbebd4803477563eff03d719605ed37a8e44d;hpb=07b5f97202c840d97bd9f53c62c4192a6b0adf30;ds=sidebyside diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 900235bc..d78bcf80 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -77,26 +77,25 @@ (defvar notmuch-search-history nil "Variable to store notmuch searches history.") -(defcustom notmuch-saved-searches nil +(defcustom notmuch-saved-searches '(("inbox" . "tag:inbox") + ("unread" . "tag:unread")) "A list of saved searches to display." :type '(alist :key-type string :value-type string) :group 'notmuch-hello) -(defvar notmuch-folders nil - "Deprecated name for what is now known as `notmuch-saved-searches'.") +(defcustom notmuch-archive-tags '("-inbox") + "List of tag changes to apply to a message or a thread when it is archived. -(defun notmuch-saved-searches () - "Common function for querying the notmuch-saved-searches variable. +Tags starting with \"+\" (or not starting with either \"+\" or +\"-\") in the list will be added, and tags starting with \"-\" +will be removed from the message or thread being archived. -We do this as a function to support the old name of the -variable (`notmuch-folders') as well as for the default value if -the user hasn't set this variable with the old or new value." - (if notmuch-saved-searches - notmuch-saved-searches - (if notmuch-folders - notmuch-folders - '(("inbox" . "tag:inbox") - ("unread" . "tag:unread"))))) +For example, if you wanted to remove an \"inbox\" tag and add an +\"archived\" tag, you would set: + (\"-inbox\" \"+archived\")" + :type '(repeat string) + :group 'notmuch-search + :group 'notmuch-show) (defun notmuch-version () "Return a string with the notmuch version number." @@ -147,16 +146,36 @@ the user hasn't set this variable with the old or new value." "[No Subject]" subject))) +(defun notmuch-escape-boolean-term (term) + "Escape a boolean term for use in a query. + +The caller is responsible for prepending the term prefix and a +colon. This performs minimal escaping in order to produce +user-friendly queries." + + (save-match-data + (if (or (equal term "") + (string-match "[ ()]\\|^\"" term)) + ;; Requires escaping + (concat "\"" (replace-regexp-in-string "\"" "\"\"" term t t) "\"") + term))) + (defun notmuch-id-to-query (id) "Return a query that matches the message with id ID." - (concat "id:\"" (replace-regexp-in-string "\"" "\"\"" id t t) "\"")) + (concat "id:" (notmuch-escape-boolean-term id))) ;; (defun notmuch-common-do-stash (text) "Common function to stash text in kill ring, and display in minibuffer." - (kill-new text) - (message "Stashed: %s" text)) + (if text + (progn + (kill-new text) + (message "Stashed: %s" text)) + ;; There is nothing to stash so stash an empty string so the user + ;; doesn't accidentally paste something else somewhere. + (kill-new "") + (message "Nothing to stash!"))) ;; @@ -240,6 +259,19 @@ the given type." (or (plist-get part :content) (notmuch-get-bodypart-internal (notmuch-id-to-query (plist-get msg :id)) nth process-crypto))) +;; Workaround: The call to `mm-display-part' below triggers a bug in +;; Emacs 24 if it attempts to use the shr renderer to display an HTML +;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and +;; Fedora 17, though unreproducable in other configurations). +;; `mm-shr' references the variable `gnus-inhibit-images' without +;; first loading gnus-art, which defines it, resulting in a +;; void-variable error. Hence, we advise `mm-shr' to ensure gnus-art +;; is loaded. +(if (>= emacs-major-version 24) + (defadvice mm-shr (before load-gnus-arts activate) + (require 'gnus-art nil t) + (ad-disable-advice 'mm-shr 'before 'load-gnus-arts))) + (defun notmuch-mm-display-part-inline (msg part nth content-type process-crypto) "Use the mm-decode/mm-view functions to display a part in the current buffer, if possible." @@ -284,6 +316,115 @@ string), a property list of face attributes, or a list of these." (put-text-property pos next 'face (cons face cur)) (setq pos next))))) +(defun notmuch-logged-error (msg &optional extra) + "Log MSG and EXTRA to *Notmuch errors* and signal MSG. + +This logs MSG and EXTRA to the *Notmuch errors* buffer and +signals MSG as an error. If EXTRA is non-nil, text referring the +user to the *Notmuch errors* buffer will be appended to the +signaled error. This function does not return." + + (with-current-buffer (get-buffer-create "*Notmuch errors*") + (goto-char (point-max)) + (unless (bobp) + (newline)) + (save-excursion + (insert "[" (current-time-string) "]\n" msg) + (unless (bolp) + (newline)) + (when extra + (insert extra) + (unless (bolp) + (newline))))) + (error "%s" (concat msg (when extra + " (see *Notmuch errors* for more details)")))) + +(defun notmuch-check-async-exit-status (proc msg) + "If PROC exited abnormally, pop up an error buffer and signal an error. + +This is a wrapper around `notmuch-check-exit-status' for +asynchronous process sentinels. PROC and MSG must be the +arguments passed to the sentinel." + (let ((exit-status + (case (process-status proc) + ((exit) (process-exit-status proc)) + ((signal) msg)))) + (when exit-status + (notmuch-check-exit-status exit-status (process-command proc))))) + +(defun notmuch-check-exit-status (exit-status command &optional output err-file) + "If EXIT-STATUS is non-zero, pop up an error buffer and signal an error. + +If EXIT-STATUS is non-zero, pop up a notmuch error buffer +describing the error and signal an Elisp error. EXIT-STATUS must +be a number indicating the exit status code of a process or a +string describing the signal that terminated the process (such as +returned by `call-process'). COMMAND must be a list giving the +command and its arguments. OUTPUT, if provided, is a string +giving the output of command. ERR-FILE, if provided, is the name +of a file containing the error output of command. OUTPUT and the +contents of ERR-FILE will be included in the error message." + + (cond + ((eq exit-status 0) t) + ((eq exit-status 20) + (notmuch-logged-error "notmuch CLI version mismatch +Emacs requested an older output format than supported by the notmuch CLI. +You may need to restart Emacs or upgrade your notmuch Emacs package.")) + ((eq exit-status 21) + (notmuch-logged-error "notmuch CLI version mismatch +Emacs requested a newer output format than supported by the notmuch CLI. +You may need to restart Emacs or upgrade your notmuch package.")) + (t + (let* ((err (when err-file + (with-temp-buffer + (insert-file-contents err-file) + (unless (eobp) + (buffer-string))))) + (extra + (concat + "command: " (mapconcat #'shell-quote-argument command " ") "\n" + (if (integerp exit-status) + (format "exit status: %s\n" exit-status) + (format "exit signal: %s\n" exit-status)) + (when err + (concat "stderr:\n" err)) + (when output + (concat "stdout:\n" output))))) + (if err + ;; We have an error message straight from the CLI. + (notmuch-logged-error + (replace-regexp-in-string "\\s $" "" err) extra) + ;; We only have combined output from the CLI; don't inundate + ;; the user with it. Mimic `process-lines'. + (notmuch-logged-error (format "%s exited with status %s" + (car command) exit-status) + extra)) + ;; `notmuch-logged-error' does not return. + )))) + +(defun notmuch-call-notmuch-json (&rest args) + "Invoke `notmuch-command' with `args' and return the parsed JSON output. + +The returned output will represent objects using property lists +and arrays as lists. If notmuch exits with a non-zero status, +this will pop up a buffer containing notmuch's output and signal +an error." + + (with-temp-buffer + (let ((err-file (make-temp-file "nmerr"))) + (unwind-protect + (let ((status (apply #'call-process + notmuch-command nil (list t err-file) nil args))) + (notmuch-check-exit-status status (cons notmuch-command args) + (buffer-string) err-file) + (goto-char (point-min)) + (let ((json-object-type 'plist) + (json-array-type 'list) + (json-false 'nil)) + (json-read))) + (delete-file err-file))))) + ;; Compatibility functions for versions of emacs before emacs 23. ;; ;; Both functions here were copied from emacs 23 with the following copyright: @@ -314,6 +455,16 @@ was called." ;; Incremental JSON parsing +;; These two variables are internal variables to the parsing +;; routines. They are always used buffer local but need to be declared +;; globally to avoid compiler warnings. + +(defvar notmuch-json-parser nil + "Internal incremental JSON parser object: local to the buffer being parsed.") + +(defvar notmuch-json-state nil + "State of the internal JSON parser: local to the buffer being parsed.") + (defun notmuch-json-create-parser (buffer) "Return a streaming JSON parser that consumes input from BUFFER. @@ -423,15 +574,19 @@ Entering JSON objects is currently unimplemented." (with-current-buffer (notmuch-json-buffer jp) ;; Disallow terminators (setf (notmuch-json-allow-term jp) nil) - (or (notmuch-json-scan-to-value jp) - (if (/= (char-after) ?\[) - (signal 'json-readtable-error (list "expected '['")) - (forward-char) - (push ?\] (notmuch-json-term-stack jp)) - ;; Expect a value or terminator next - (setf (notmuch-json-next jp) 'expect-value - (notmuch-json-allow-term jp) t) - t)))) + ;; Save "next" so we can restore it if there's a syntax error + (let ((saved-next (notmuch-json-next jp))) + (or (notmuch-json-scan-to-value jp) + (if (/= (char-after) ?\[) + (progn + (setf (notmuch-json-next jp) saved-next) + (signal 'json-readtable-error (list "expected '['"))) + (forward-char) + (push ?\] (notmuch-json-term-stack jp)) + ;; Expect a value or terminator next + (setf (notmuch-json-next jp) 'expect-value + (notmuch-json-allow-term jp) t) + t))))) (defun notmuch-json-read (jp) "Parse the value at point in JP's buffer. @@ -508,6 +663,62 @@ of the buffer if there is only trailing whitespace." (unless (eobp) (signal 'json-error (list "Trailing garbage following JSON data"))))) +(defun notmuch-json-parse-partial-list (result-function error-function results-buf) + "Parse a partial JSON list from current buffer. + +This function consumes a JSON list from the current buffer, +applying RESULT-FUNCTION in buffer RESULT-BUFFER to each complete +value in the list. It operates incrementally and should be +called whenever the buffer has been extended with additional +data. + +If there is a syntax error, this will attempt to resynchronize +with the input and will apply ERROR-FUNCTION in buffer +RESULT-BUFFER to any input that was skipped. + +It sets up all the needed internal variables: the caller just +needs to call it with point in the same place that the parser +left it." + (let (done) + (unless (local-variable-p 'notmuch-json-parser) + (set (make-local-variable 'notmuch-json-parser) + (notmuch-json-create-parser (current-buffer))) + (set (make-local-variable 'notmuch-json-state) 'begin)) + (while (not done) + (condition-case nil + (case notmuch-json-state + ((begin) + ;; Enter the results list + (if (eq (notmuch-json-begin-compound + notmuch-json-parser) 'retry) + (setq done t) + (setq notmuch-json-state 'result))) + ((result) + ;; Parse a result + (let ((result (notmuch-json-read notmuch-json-parser))) + (case result + ((retry) (setq done t)) + ((end) (setq notmuch-json-state 'end)) + (otherwise (with-current-buffer results-buf + (funcall result-function result)))))) + ((end) + ;; Any trailing data is unexpected + (notmuch-json-eof notmuch-json-parser) + (setq done t))) + (json-error + ;; Do our best to resynchronize and ensure forward + ;; progress + (let ((bad (buffer-substring (line-beginning-position) + (line-end-position)))) + (forward-line) + (with-current-buffer results-buf + (funcall error-function "%s" bad)))))) + ;; Clear out what we've parsed + (delete-region (point-min) (point)))) + + + + (provide 'notmuch-lib) ;; Local Variables: