]> git.notmuchmail.org Git - notmuch/commitdiff
Merge branch 'release'
authorDavid Bremner <david@tethera.net>
Fri, 25 Dec 2020 16:56:57 +0000 (12:56 -0400)
committerDavid Bremner <david@tethera.net>
Fri, 25 Dec 2020 16:56:57 +0000 (12:56 -0400)
24 files changed:
emacs/coolj.el
emacs/notmuch-address.el
emacs/notmuch-company.el
emacs/notmuch-compat.el
emacs/notmuch-crypto.el
emacs/notmuch-draft.el
emacs/notmuch-hello.el
emacs/notmuch-jump.el
emacs/notmuch-lib.el
emacs/notmuch-maildir-fcc.el
emacs/notmuch-mua.el
emacs/notmuch-parser.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch.el
lib/Makefile.local
lib/database-private.h
lib/database.cc
lib/features.cc [new file with mode: 0644]
lib/open.cc [new file with mode: 0644]
lib/prefix.cc [new file with mode: 0644]
test/T140-excludes.sh
test/T590-libconfig.sh

index 39a8de2bcd2dfb18bcb3426f39e361a9e0eba8be..0385872fb655340c6d10737ed9b1250698e011f2 100644 (file)
@@ -50,9 +50,7 @@ Otherwise respect `fill-column'."
   :group 'coolj
   :type 'regexp)
 
-(defvar coolj-wrap-point nil)
-
-(make-variable-buffer-local 'coolj-wrap-point)
+(defvar-local coolj-wrap-point nil)
 
 (defun coolj-determine-prefix ()
   "Determine the prefix for the current line."
index 8a6d299cb3061bc7234c932a59a73f7e6da87e21..71985ed79dfcce86985002abf2c97e7d9ddb0a65 100644 (file)
@@ -36,9 +36,9 @@
 This variable is set by calling `notmuch-address-harvest'.")
 
 (defvar notmuch-address-full-harvest-finished nil
-  "t indicates that full completion address harvesting has been finished.
-Use notmuch-address--harvest-ready to access as that will load a
-saved hash if necessary (and available).")
+  "Whether full completion address harvesting has finished.
+Use `notmuch-address--harvest-ready' to access as that will load
+saved hash if necessary (and available).")
 
 (defun notmuch-address--harvest-ready ()
   "Return t if there is a full address hash available.
@@ -250,25 +250,6 @@ requiring external commands."
        (ding))))
    (t nil)))
 
-;; Copied from `w3m-which-command'.
-(defun notmuch-address-locate-command (command)
-  "Return non-nil if `command' is an executable either on
-`exec-path' or an absolute pathname."
-  (and (stringp command)
-       (if (and (file-name-absolute-p command)
-               (file-executable-p command))
-          command
-        (setq command (file-name-nondirectory command))
-        (catch 'found-command
-          (let (bin)
-            (dolist (dir exec-path)
-              (setq bin (expand-file-name command dir))
-              (when (or (and (file-executable-p bin)
-                             (not (file-directory-p bin)))
-                        (and (file-executable-p (setq bin (concat bin ".exe")))
-                             (not (file-directory-p bin))))
-                (throw 'found-command bin))))))))
-
 (defun notmuch-address-harvest-addr (result)
   (let ((name-addr (plist-get result :name-addr)))
     (puthash name-addr t notmuch-address-completions)))
@@ -294,7 +275,7 @@ The car is a partial harvest, and the cdr is a full harvest.")
   "Collect addresses completion candidates.
 
 It queries the notmuch database for messages sent/received (as
-configured with `notmuch-address-command`) by the user, collects
+configured with `notmuch-address-command') by the user, collects
 destination/source addresses from those messages and stores them
 in `notmuch-address-completions'.
 
index 9ee8ceca2922076ce3e459f098939c0e3025c289..b50e73c8899bda57525bff9528920f17c479bc93 100644 (file)
@@ -36,8 +36,8 @@
 
 (require 'notmuch-lib)
 
-(defvar notmuch-company-last-prefix nil)
-(make-variable-buffer-local 'notmuch-company-last-prefix)
+(defvar-local notmuch-company-last-prefix nil)
+
 (declare-function company-begin-backend "company")
 (declare-function company-grab "company")
 (declare-function company-mode "company")
@@ -55,8 +55,7 @@
 ;;;###autoload
 (defun notmuch-company-setup ()
   (company-mode)
-  (make-local-variable 'company-backends)
-  (setq company-backends '(notmuch-company))
+  (setq-local company-backends '(notmuch-company))
   ;; Disable automatic company completion unless an internal
   ;; completion method is configured. Company completion (using
   ;; internal completion) can still be accessed via standard company
index 3ede6b36aeae6d7c0e7bfa90dfb9331e1dbb7ad6..2975f4c2ec1bffb31a4e1e4110544b82a2d2a01d 100644 (file)
 
 ;;; Code:
 
-;; emacs master has a bugfix for folding long headers when sending
-;; messages. Include the fix for earlier versions of emacs. To avoid
-;; interfering with gnus we only run the hook when called from
-;; notmuch-message-mode.
+;; Before Emacs 26.1 lines that are longer than 998 octets were not.
+;; folded. Commit 77bbca8c82f6e553c42abbfafca28f55fc995d00 fixed
+;; that. Until we drop support for Emacs 25 we have to backport that
+;; fix. To avoid interfering with Gnus we only run the hook when
+;; called from notmuch-message-mode.
 
 (declare-function mail-header-fold-field "mail-parse" nil)
 
index 276c98594e3db94c115845600064d81d2d7e800c..9e6f3a9d77a5b26a983ba83dbd21be4690c4866f 100644 (file)
@@ -27,7 +27,7 @@
 (declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
 
 (defcustom notmuch-crypto-process-mime t
-  "Should cryptographic MIME parts be processed?
+  "Whether to process cryptographic MIME parts.
 
 If this variable is non-nil signatures in multipart/signed
 messages will be verified and multipart/encrypted parts will be
@@ -46,7 +46,7 @@ mode."
   :group 'notmuch-crypto)
 
 (defcustom notmuch-crypto-get-keys-asynchronously t
-  "Retrieve gpg keys asynchronously."
+  "Whether to retrieve openpgp keys asynchronously."
   :type 'boolean
   :group 'notmuch-crypto)
 
@@ -103,8 +103,7 @@ mode."
   :supertype 'notmuch-button-type)
 
 (defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
-  "Insert a button describing the signature status SIGSTATUS sent
-by user FROM."
+  "Insert a button describing the signature status SIGSTATUS sent by user FROM."
   (let* ((status (plist-get sigstatus :status))
         (show-button t)
         (face 'notmuch-crypto-signature-unknown)
index 283830ad0d0097c2aa529cc754ea41cd90002281..f928be876a906c013f9ec13d345e9d95211461ba 100644 (file)
@@ -89,9 +89,8 @@ like they are intended to be sent encrypted
   "<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)"
   "Regular expression matching mml tags indicating encryption of part or message.")
 
-(defvar notmuch-draft-id nil
+(defvar-local notmuch-draft-id nil
   "Message-id of the most recent saved draft of this message.")
-(make-variable-buffer-local 'notmuch-draft-id)
 
 (defun notmuch-draft--mark-deleted ()
   "Tag the last saved draft deleted.
@@ -101,7 +100,7 @@ Used when a new version is saved, or the message is sent."
     (notmuch-tag notmuch-draft-id '("+deleted"))))
 
 (defun notmuch-draft-quote-some-mml ()
-  "Quote the mml tags in `notmuch-draft-quoted-tags`."
+  "Quote the mml tags in `notmuch-draft-quoted-tags'."
   (save-excursion
     ;; First we deal with any secure tag separately.
     (message-goto-body)
@@ -122,7 +121,7 @@ Used when a new version is saved, or the message is sent."
          (insert "!"))))))
 
 (defun notmuch-draft-unquote-some-mml ()
-  "Unquote the mml tags in `notmuch-draft-quoted-tags`."
+  "Unquote the mml tags in `notmuch-draft-quoted-tags'."
   (save-excursion
     (when notmuch-draft-quoted-tags
       (let ((re (concat "<#!+/?\\("
@@ -136,7 +135,7 @@ Used when a new version is saved, or the message is sent."
     (let (secure-tag)
       (save-restriction
        (message-narrow-to-headers)
-       (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" 't))
+       (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" t))
        (message-remove-header "X-Notmuch-Emacs-Secure"))
       (message-goto-body)
       (when secure-tag
@@ -146,7 +145,7 @@ Used when a new version is saved, or the message is sent."
   "Returns t if there is an mml secure tag."
   (save-excursion
     (message-goto-body)
-    (re-search-forward notmuch-draft-encryption-tag-regex nil 't)))
+    (re-search-forward notmuch-draft-encryption-tag-regex nil t)))
 
 (defun notmuch-draft--query-encryption ()
   "Checks if we should save a message that should be encrypted.
@@ -174,7 +173,7 @@ Really save and index an unencrypted copy? ")
   "Save the current draft message in the notmuch database.
 
 This saves the current message in the database with tags
-`notmuch-draft-tags` (in addition to any default tags
+`notmuch-draft-tags' (in addition to any default tags
 applied to newly inserted messages)."
   (interactive)
   (when (notmuch-draft--has-encryption-tag)
@@ -185,7 +184,7 @@ applied to newly inserted messages)."
      ;; so that it is easier to search for the message, and the
      ;; latter so we have a way of accessing the saved message (for
      ;; example to delete it at a later time). We check that the
-     ;; user has these in `message-deletable-headers` (the default)
+     ;; user has these in `message-deletable-headers' (the default)
      ;; as otherwise they are doing something strange and we
      ;; shouldn't interfere. Note, since we are doing this in a new
      ;; buffer we don't change the version in the compose buffer.
@@ -208,7 +207,7 @@ applied to newly inserted messages)."
      (notmuch-draft-quote-some-mml)
      (notmuch-maildir-setup-message-for-saving)
      (notmuch-maildir-notmuch-insert-current-buffer
-      notmuch-draft-folder 't notmuch-draft-tags))
+      notmuch-draft-folder t notmuch-draft-tags))
     ;; We are now back in the original compose buffer. Note the
     ;; function notmuch-call-notmuch-process (called by
     ;; notmuch-maildir-notmuch-insert-current-buffer) signals an error
index bb60a890f85e1a1e8680363f136d15761b4c728e..fa31694ff0f79a7f2781849394390a673f75aade 100644 (file)
@@ -21,8 +21,7 @@
 
 ;;; Code:
 
-(eval-when-compile (require 'cl-lib))
-
+(require 'cl-lib)
 (require 'widget)
 (require 'wid-edit) ; For `widget-forward'.
 
@@ -138,8 +137,8 @@ a plist. Supported properties are
                    shown. If not present then the :query property
                    is used.
   :sort-order      Specify the sort order to be used for the search.
-                   Possible values are 'oldest-first 'newest-first or
-                   nil. Nil means use the default sort order.
+                   Possible values are `oldest-first', `newest-first'
+                   or nil. Nil means use the default sort order.
   :search-type     Specify whether to run the search in search-mode,
                    tree mode or unthreaded mode. Set to 'tree to specify tree
                    mode, 'unthreaded to specify unthreaded mode, and set to nil
@@ -282,7 +281,7 @@ International Bureau of Weights and Measures."
   :group 'notmuch-hello
   :group 'notmuch-hooks)
 
-(defvar notmuch-hello-url "https://notmuchmail.org"
+(defconst notmuch-hello-url "https://notmuchmail.org"
   "The `notmuch' web site.")
 
 (defvar notmuch-hello-custom-section-options
@@ -372,8 +371,7 @@ supported for \"Customized queries section\" items."
   "List of sections titles whose contents are hidden.")
 
 (defvar notmuch-hello-first-run t
-  "True if `notmuch-hello' is run for the first time, set to nil
-afterwards.")
+  "True if `notmuch-hello' is run for the first time, set to nil afterwards.")
 
 (defun notmuch-hello-nice-number (n)
   (let (result)
@@ -387,15 +385,9 @@ afterwards.")
                     (format "%s%03d" notmuch-hello-thousands-separator elem))
                   (cdr result)))))
 
-(defun notmuch-hello-trim (search)
-  "Trim whitespace."
-  (if (string-match "^[[:space:]]*\\(.*[^[:space:]]\\)[[:space:]]*$" search)
-      (match-string 1 search)
-    search))
-
 (defun notmuch-hello-search (&optional search)
   (unless (null search)
-    (setq search (notmuch-hello-trim search))
+    (setq search (string-trim search))
     (let ((history-delete-duplicates t))
       (add-to-history 'notmuch-search-history search)))
   (notmuch-search search notmuch-search-oldest-first))
@@ -549,21 +541,19 @@ options will be handled as specified for
 --batch'. In general we recommend running matching versions of
 the CLI and emacs interface."))
     (goto-char (point-min))
-    (notmuch-remove-if-not
-     #'identity
-     (mapcar
-      (lambda (elem)
-       (let* ((elem-plist (notmuch-hello-saved-search-to-plist elem))
-              (search-query (plist-get elem-plist :query))
-              (filtered-query (notmuch-hello-filtered-query
-                               search-query (plist-get options :filter)))
-              (message-count (prog1 (read (current-buffer))
-                               (forward-line 1))))
-         (when (and filtered-query (or (plist-get options :show-empty-searches)
-                                       (> message-count 0)))
-           (setq elem-plist (plist-put elem-plist :query filtered-query))
-           (plist-put elem-plist :count message-count))))
-      query-list))))
+    (cl-mapcan
+     (lambda (elem)
+       (let* ((elem-plist (notmuch-hello-saved-search-to-plist elem))
+             (search-query (plist-get elem-plist :query))
+             (filtered-query (notmuch-hello-filtered-query
+                              search-query (plist-get options :filter)))
+             (message-count (prog1 (read (current-buffer))
+                              (forward-line 1))))
+        (when (and filtered-query (or (plist-get options :show-empty-searches)
+                                      (> message-count 0)))
+          (setq elem-plist (plist-put elem-plist :query filtered-query))
+          (list (plist-put elem-plist :count message-count)))))
+     query-list)))
 
 (defun notmuch-hello-insert-buttons (searches)
   "Insert buttons for SEARCHES.
@@ -651,39 +641,19 @@ with `notmuch-hello-query-counts'."
       ;; Refresh hello as soon as we get back to redisplay.  On Emacs
       ;; 24, we can't do it right here because something in this
       ;; hook's call stack overrides hello's point placement.
+      ;; FIXME And on Emacs releases that we still support?
       (run-at-time nil nil #'notmuch-hello t))
     (unless hello-buf
       ;; Clean up hook
       (remove-hook 'window-configuration-change-hook
                   #'notmuch-hello-window-configuration-change))))
 
-;; the following variable is defined as being defconst in notmuch-version.el
-(defvar notmuch-emacs-version)
-
-(defun notmuch-hello-versions ()
-  "Display the notmuch version(s)."
-  (interactive)
-  (let ((notmuch-cli-version (notmuch-cli-version)))
-    (message "notmuch version %s"
-            (if (string= notmuch-emacs-version notmuch-cli-version)
-                notmuch-cli-version
-              (concat notmuch-cli-version
-                      " (emacs mua version " notmuch-emacs-version ")")))))
-
 (defvar notmuch-hello-mode-map
-  (let ((map (if (fboundp 'make-composed-keymap)
-                ;; Inherit both widget-keymap and
-                ;; notmuch-common-keymap. We have to use
-                ;; make-sparse-keymap to force this to be a new
-                ;; keymap (so that when we modify map it does not
-                ;; modify widget-keymap).
-                (make-composed-keymap (list (make-sparse-keymap) widget-keymap))
-              ;; Before Emacs 24, keymaps didn't support multiple
-              ;; inheritance,, so just copy the widget keymap since
-              ;; it's unlikely to change.
-              (copy-keymap widget-keymap))))
+  ;; Inherit both widget-keymap and notmuch-common-keymap.  We have
+  ;; to use make-sparse-keymap to force this to be a new keymap (so
+  ;; that when we modify map it does not modify widget-keymap).
+  (let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap))))
     (set-keymap-parent map notmuch-common-keymap)
-    (define-key map "v" 'notmuch-hello-versions)
     (define-key map (kbd "<C-tab>") 'widget-backward)
     map)
   "Keymap for \"notmuch hello\" buffers.")
@@ -725,12 +695,12 @@ Complete list of currently available key bindings:
 
 (defun notmuch-hello-generate-tag-alist (&optional hide-tags)
   "Return an alist from tags to queries to display in the all-tags section."
-  (mapcar (lambda (tag)
-           (cons tag (concat "tag:" (notmuch-escape-boolean-term tag))))
-         (notmuch-remove-if-not
-          (lambda (tag)
-            (not (member tag hide-tags)))
-          (process-lines notmuch-command "search" "--output=tags" "*"))))
+  (cl-mapcan (lambda (tag)
+              (and (not (member tag hide-tags))
+                   (list (cons tag
+                               (concat "tag:"
+                                       (notmuch-escape-boolean-term tag))))))
+            (process-lines notmuch-command "search" "--output=tags" "*")))
 
 (defun notmuch-hello-insert-header ()
   "Insert the default notmuch-hello header."
@@ -1014,11 +984,6 @@ following:
   (run-hooks 'notmuch-hello-refresh-hook)
   (setq notmuch-hello-first-run nil))
 
-(defun notmuch-folder ()
-  "Deprecated function for invoking notmuch---calling `notmuch' is preferred now."
-  (interactive)
-  (notmuch-hello))
-
 ;;
 
 (provide 'notmuch-hello)
index 1e2d0497949322c26cd2c453ae215336551156d6..ff622055f0cf5c4058caec4c8d5c71253e50e5f5 100644 (file)
 (require 'notmuch-lib)
 (require 'notmuch-hello)
 
-(eval-and-compile
-  (unless (fboundp 'window-body-width)
-    ;; Compatibility for Emacs pre-24
-    (defalias 'window-body-width 'window-width)))
-
 ;;;###autoload
 (defun notmuch-jump-search ()
   "Jump to a saved search by shortcut key.
index 118faf1e37bddb1a9e5b0fad8374ac8fb4361941..e23999ad74a4dcbc29ec0c6fe0425c58b2c260a4 100644 (file)
@@ -147,6 +147,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
 (defvar notmuch-common-keymap
   (let ((map (make-sparse-keymap)))
     (define-key map "?" 'notmuch-help)
+    (define-key map "v" 'notmuch-version)
     (define-key map "q" 'notmuch-bury-or-kill-this-buffer)
     (define-key map "s" 'notmuch-search)
     (define-key map "t" 'notmuch-search-by-tag)
@@ -218,6 +219,21 @@ on the command line, and then retry your notmuch command")))
        (match-string 2 long-string)
       "unknown")))
 
+(defvar notmuch-emacs-version)
+
+(defun notmuch-version ()
+  "Display the notmuch version.
+The versions of the Emacs package and the `notmuch' executable
+should match, but if and only if they don't, then this command
+displays both values separately."
+  (interactive)
+  (let ((cli-version (notmuch-cli-version)))
+    (message "notmuch version %s"
+            (if (string= notmuch-emacs-version cli-version)
+                cli-version
+              (concat cli-version
+                      " (emacs mua version " notmuch-emacs-version ")")))))
+
 (defun notmuch-config-get (item)
   "Return a value from the notmuch configuration."
   (let* ((val (notmuch-command-to-string "config" "get" item))
@@ -271,18 +287,6 @@ it, in which case it is killed."
       (bury-buffer)
     (kill-buffer)))
 
-(defun notmuch-documentation-first-line (symbol)
-  "Return the first line of the documentation string for SYMBOL."
-  (let ((doc (documentation symbol)))
-    (if doc
-       (with-temp-buffer
-         (insert (documentation symbol t))
-         (goto-char (point-min))
-         (let ((beg (point)))
-           (end-of-line)
-           (buffer-substring beg (point))))
-      "")))
-
 (defun notmuch-prefix-key-description (key)
   "Given a prefix key code, return a human-readable string representation.
 
@@ -315,7 +319,10 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
                  (or (and (symbolp binding)
                           (get binding 'notmuch-doc))
                      (and (functionp binding)
-                          (notmuch-documentation-first-line binding))))
+                          (let ((doc (documentation binding)))
+                            (and doc
+                                 (string-match "\\`.+" doc)
+                                 (match-string 0 doc))))))
            tail)))
   tail)
 
@@ -428,9 +435,8 @@ of its command symbol."
          (insert desc)))
       (pop-to-buffer (help-buffer)))))
 
-(defvar notmuch-buffer-refresh-function nil
+(defvar-local notmuch-buffer-refresh-function nil
   "Function to call to refresh the current buffer.")
-(make-variable-buffer-local 'notmuch-buffer-refresh-function)
 
 (defun notmuch-refresh-this-buffer ()
   "Refresh the current buffer."
@@ -518,15 +524,6 @@ This replaces spaces, percents, and double quotes in STR with
 
 ;;
 
-(defun notmuch-remove-if-not (predicate list)
-  "Return a copy of LIST with all items not satisfying PREDICATE removed."
-  (let (out)
-    (while list
-      (when (funcall predicate (car list))
-       (push (car list) out))
-      (setq list (cdr list)))
-    (nreverse out)))
-
 (defun notmuch-plist-delete (plist property)
   (let* ((xplist (cons nil plist))
         (pred xplist))
@@ -536,14 +533,10 @@ This replaces spaces, percents, and double quotes in STR with
       (setq pred (cddr pred)))
     (cdr xplist)))
 
-(defun notmuch-split-content-type (content-type)
-  "Split content/type into 'content' and 'type'."
-  (split-string content-type "/"))
-
 (defun notmuch-match-content-type (t1 t2)
   "Return t if t1 and t2 are matching content types, taking wildcards into account."
-  (let ((st1 (notmuch-split-content-type t1))
-       (st2 (notmuch-split-content-type t2)))
+  (let ((st1 (split-string t1 "/"))
+       (st2 (split-string t2 "/")))
     (if (or (string= (cadr st1) "*")
            (string= (cadr st2) "*"))
        ;; Comparison of content types should be case insensitive.
@@ -653,18 +646,6 @@ If CACHE is non-nil, the content of this part will be saved in
 MSG (if it isn't already)."
   (notmuch--get-bodypart-raw msg part process-crypto nil cache))
 
-;; 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 unreproducible 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.
-(define-advice mm-shr (:before (_handle) notmuch--load-gnus-args)
-  "Require `gnus-art' since we use its variables."
-  (require 'gnus-art nil t))
-
 (defun notmuch-mm-display-part-inline (msg part content-type process-crypto)
   "Use the mm-decode/mm-view functions to display a part in the
 current buffer, if possible."
@@ -1007,10 +988,7 @@ status."
                         (find-file-noselect err-file))))
     (when err-buffer (kill-buffer err-buffer))))
 
-;; This variable is used only buffer local, but it needs to be
-;; declared globally first to avoid compiler warnings.
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+(defvar-local notmuch-show-process-crypto nil)
 
 (defun notmuch-interactive-region ()
   "Return the bounds of the current interactive region.
index a9103a2069932b8fb050898ddf6b74578d5c428d..32b8100e7fed0a4d1bfa3ec03cbd9d329ce6fe72 100644 (file)
@@ -76,7 +76,7 @@ directory if it does not exist yet when sending a mail."
   :require 'notmuch-fcc-initialization
   :group 'notmuch-send)
 
-(defcustom notmuch-maildir-use-notmuch-insert 't
+(defcustom notmuch-maildir-use-notmuch-insert t
   "Should fcc use notmuch insert instead of simple fcc."
   :type '(choice :tag "Fcc Method"
                 (const :tag "Use notmuch insert" t)
@@ -196,7 +196,7 @@ This is a rearranged version of message mode's message-do-fcc."
 (defun notmuch-fcc-handler (fcc-header)
   "Store message with notmuch insert or normal (file) fcc.
 
-If `notmuch-maildir-use-notmuch-insert` is set then store the
+If `notmuch-maildir-use-notmuch-insert' is set then store the
 message using notmuch insert. Otherwise store the message using
 normal fcc."
   (message "Doing Fcc...")
@@ -246,8 +246,8 @@ If CREATE is non-nil then create the folder if necessary."
 \(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e))))
         (cl-case response
           (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
-          (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
-          (?i 't)
+          (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header t))
+          (?i t)
           (?e (notmuch-maildir-fcc-with-notmuch-insert
                (read-from-minibuffer "Fcc header: " fcc-header)))))))))
 
@@ -322,7 +322,7 @@ if successful, nil if not."
 It offers the user a chance to correct the header, or filesystem,
 if needed."
   (if (notmuch-maildir-fcc-dir-is-maildir-p fcc-header)
-      (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't)
+      (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header t)
     ;; The fcc-header is not a valid maildir see if the user wants to
     ;; fix it in some way.
     (let* ((prompt (format "Fcc %s is not a maildir: \
@@ -335,7 +335,7 @@ if needed."
              (message "No permission to create %s." fcc-header)
              (sit-for 2))
            (notmuch-maildir-fcc-file-fcc fcc-header))
-       (?i 't)
+       (?i t)
        (?e (notmuch-maildir-fcc-file-fcc
             (read-from-minibuffer "Fcc header: " fcc-header)))))))
 
index 03c7cc97364f83c4e9f24d7193e444c10b423653..c38cb5aa147e0e523f2fbddb4811a6a78483c8c2 100644 (file)
@@ -47,8 +47,7 @@
   :group 'notmuch-hooks)
 
 (defcustom notmuch-mua-compose-in 'current-window
-  (concat
-   "Where to create the mail buffer used to compose a new message.
+  "Where to create the mail buffer used to compose a new message.
 Possible values are `current-window' (default), `new-window' and
 `new-frame'. If set to `current-window', the mail buffer will be
 displayed in the current window, so the old buffer will be
@@ -57,18 +56,14 @@ or `new-frame', the mail buffer will be displayed in a new
 window/frame that will be destroyed when the buffer is killed.
 You may want to customize `message-kill-buffer-on-exit'
 accordingly."
-   (when (< emacs-major-version 24)
-     " Due to a known bug in Emacs 23, you should not set
-this to `new-window' if `message-kill-buffer-on-exit' is
-disabled: this would result in an incorrect behavior."))
   :group 'notmuch-send
   :type '(choice (const :tag "Compose in the current window" current-window)
                 (const :tag "Compose mail in a new window"  new-window)
                 (const :tag "Compose mail in a new frame"   new-frame)))
 
 (defcustom notmuch-mua-user-agent-function nil
-  "Function used to generate a `User-Agent:' string. If this is
-`nil' then no `User-Agent:' will be generated."
+  "Function used to generate a `User-Agent:' string.
+If this is `nil' then no `User-Agent:' will be generated."
   :type '(choice (const :tag "No user agent string" nil)
                 (const :tag "Full" notmuch-mua-user-agent-full)
                 (const :tag "Notmuch" notmuch-mua-user-agent-notmuch)
@@ -78,8 +73,7 @@ disabled: this would result in an incorrect behavior."))
   :group 'notmuch-send)
 
 (defcustom notmuch-mua-hidden-headers nil
-  "Headers that are added to the `message-mode' hidden headers
-list."
+  "Headers that are added to the `message-mode' hidden headers list."
   :type '(repeat string)
   :group 'notmuch-send)
 
@@ -88,10 +82,11 @@ list."
   :group 'notmuch)
 
 (defcustom notmuch-mua-cite-function 'message-cite-original
-  "*Function for citing an original message.
+  "Function for citing an original message.
+
 Predefined functions include `message-cite-original' and
-`message-cite-original-without-signature'.
-Note that these functions use `mail-citation-hook' if that is non-nil."
+`message-cite-original-without-signature'.  Note that these
+functions use `mail-citation-hook' if that is non-nil."
   :type '(radio (function-item message-cite-original)
                (function-item message-cite-original-without-signature)
                (function-item sc-cite-original)
@@ -172,8 +167,7 @@ Typically this is added to `notmuch-mua-send-hook'."
        (t (error "Invalid value for `notmuch-mua-compose-in'"))))
 
 (defun notmuch-mua-maybe-set-window-dedicated ()
-  "Set the selected window as dedicated according to
-`notmuch-mua-compose-in'."
+  "Set the selected window as dedicated according to `notmuch-mua-compose-in'."
   (when (or (eq notmuch-mua-compose-in 'new-frame)
            (eq notmuch-mua-compose-in 'new-window))
     (set-window-dedicated-p (selected-window) t)))
@@ -212,7 +206,7 @@ Typically this is added to `notmuch-mua-send-hook'."
                                               "multipart/*")
           do (notmuch-mua-reply-crypto (plist-get part :content))))
 
-;; There is a bug in emacs 23's message.el that results in a newline
+;; There is a bug in Emacs' message.el that results in a newline
 ;; not being inserted after the References header, so the next header
 ;; is concatenated to the end of it. This function fixes the problem,
 ;; while guarding against the possibility that some current or future
@@ -324,21 +318,25 @@ Typically this is added to `notmuch-mua-send-hook'."
   (message-goto-body)
   (set-buffer-modified-p nil))
 
+(defvar notmuch-message-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
+    (define-key map (kbd "C-c C-s") #'notmuch-mua-send)
+    (define-key map (kbd "C-c C-p") #'notmuch-draft-postpone)
+    (define-key map (kbd "C-x C-s") #'notmuch-draft-save)
+    map)
+  "Keymap for `notmuch-message-mode'.")
+
 (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
   "Notmuch message composition mode. Mostly like `message-mode'."
   (notmuch-address-setup))
 
 (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
 
-(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
-(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
-(define-key notmuch-message-mode-map (kbd "C-c C-p") #'notmuch-draft-postpone)
-(define-key notmuch-message-mode-map (kbd "C-x C-s") #'notmuch-draft-save)
-
 (defun notmuch-mua-pop-to-buffer (name switch-function)
-  "Pop to buffer NAME, and warn if it already exists and is
-modified. This function is notmuch adaptation of
-`message-pop-to-buffer'."
+  "Pop to buffer NAME, and warn if it already exists and is modified.
+Like `message-pop-to-buffer' but enable `notmuch-message-mode'
+instead of `message-mode' and SWITCH-FUNCTION is mandatory."
   (let ((buffer (get-buffer name)))
     (if (and buffer
             (buffer-name buffer))
@@ -390,16 +388,10 @@ modified. This function is notmuch adaptation of
          (dolist (h other-headers other-headers)
            (when (stringp (car h))
              (setcar h (intern (capitalize (car h))))))))
-       (args (list yank-action send-actions))
        ;; Cause `message-setup-1' to do things relevant for mail,
        ;; such as observe `message-default-mail-headers'.
        (message-this-is-mail t))
-    ;; message-setup-1 in Emacs 23 does not accept return-action
-    ;; argument. Pass it only if it is supplied by the caller. This
-    ;; will never be the case when we're called by `compose-mail' in
-    ;; Emacs 23.
-    (when return-action (nconc args '(return-action)))
-    (apply 'message-setup-1 headers args))
+    (message-setup-1 headers yank-action send-actions return-action))
   (notmuch-fcc-header-setup)
   (message-sort-headers)
   (message-hide-headers)
@@ -519,10 +511,10 @@ the From: address."
 If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
 the From: address first.  If REPLY-ALL is non-nil, the message
 will be addressed to all recipients of the source message."
-  ;; In current emacs (24.3) select-active-regions is set to t by
-  ;; default. The reply insertion code sets the region to the quoted
-  ;; message to make it easy to delete (kill-region or C-w). These two
-  ;; things combine to put the quoted message in the primary selection.
+  ;; `select-active-regions' is t by default. The reply insertion code
+  ;; sets the region to the quoted message to make it easy to delete
+  ;; (kill-region or C-w). These two things combine to put the quoted
+  ;; message in the primary selection.
   ;;
   ;; This is not what the user wanted and is a privacy risk (accidental
   ;; pasting of the quoted message). We can avoid some of the problems
@@ -547,11 +539,11 @@ tag, or the user confirms they mean it."
       (goto-char (point-max))
       (or
        ;; We are always fine if there is no secure tag.
-       (not (search-backward "<#secure" nil 't))
+       (not (search-backward "<#secure" nil t))
        ;; There is a secure tag, so it must be at the start of the
        ;; body, with no secure tag earlier (i.e., in the headers).
        (and (= (point) body-start)
-           (not (search-backward "<#secure" nil 't)))
+           (not (search-backward "<#secure" nil t)))
        ;; The user confirms they means it.
        (yes-or-no-p "\
 There is a <#secure> tag not at the start of the body. It is
@@ -591,7 +583,7 @@ unencrypted.  Really send? "))))
 
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
-  (notmuch-mua-send-common arg 't))
+  (notmuch-mua-send-common arg t))
 
 (defun notmuch-mua-send (&optional arg)
   (interactive "P")
index 3aa5bd8ff1cdc45f6265d9f2133e9e8b9e070c57..4a437016dc6b47d6e6e6fa33d76c31fa6ac1e5df 100644 (file)
@@ -168,9 +168,8 @@ additional data.  The caller just needs to ensure it does not
 move point in the input buffer."
   ;; Set up the initial state
   (unless (local-variable-p 'notmuch-sexp--parser)
-    (set (make-local-variable 'notmuch-sexp--parser)
-        (notmuch-sexp-create-parser))
-    (set (make-local-variable 'notmuch-sexp--state) 'begin))
+    (setq-local notmuch-sexp--parser (notmuch-sexp-create-parser))
+    (setq-local notmuch-sexp--state 'begin))
   (let (done)
     (while (not done)
       (cl-case notmuch-sexp--state
index b08ceb973e19b0836b8a210d7b5be73dc241cc4a..056c4e30abf0097435f6bd253ab7ec208a231a40 100644 (file)
@@ -162,23 +162,17 @@ indentation."
   :type '(choice (const nil) regexp)
   :group 'notmuch-show)
 
-(defvar notmuch-show-thread-id nil)
-(make-variable-buffer-local 'notmuch-show-thread-id)
+(defvar-local notmuch-show-thread-id nil)
 
-(defvar notmuch-show-parent-buffer nil)
-(make-variable-buffer-local 'notmuch-show-parent-buffer)
+(defvar-local notmuch-show-parent-buffer nil)
 
-(defvar notmuch-show-query-context nil)
-(make-variable-buffer-local 'notmuch-show-query-context)
+(defvar-local notmuch-show-query-context nil)
 
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+(defvar-local notmuch-show-process-crypto nil)
 
-(defvar notmuch-show-elide-non-matching-messages nil)
-(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
+(defvar-local notmuch-show-elide-non-matching-messages nil)
 
-(defvar notmuch-show-indent-content t)
-(make-variable-buffer-local 'notmuch-show-indent-content)
+(defvar-local notmuch-show-indent-content t)
 
 (defvar notmuch-show-attachment-debug nil
   "If t log stdout and stderr from attachment handlers.
@@ -186,8 +180,7 @@ indentation."
 When set to nil (the default) stdout and stderr from attachment
 handlers is discarded. When set to t the stdout and stderr from
 each attachment handler is logged in buffers with names beginning
-\" *notmuch-part*\". This option requires emacs version at least
-24.3 to work.")
+\" *notmuch-part*\".")
 
 (defcustom notmuch-show-stash-mlarchive-link-alist
   '(("Gmane" . "https://mid.gmane.org/")
@@ -574,12 +567,13 @@ message at DEPTH in the current thread."
       ;; alternative (even if we can't render it).
       (push (list content-id msg part) notmuch-show--cids)))
   ;; Recurse on sub-parts
-  (let ((ctype (notmuch-split-content-type
-               (downcase (plist-get part :content-type)))))
-    (cond ((equal (car ctype) "multipart")
+  (pcase-let ((`(,content ,type)
+              (split-string (downcase (plist-get part :content-type)) "/")))
+    (cond ((equal content "multipart")
           (mapc (apply-partially #'notmuch-show--register-cids msg)
                 (plist-get part :content)))
-         ((equal ctype '("message" "rfc822"))
+         ((and (equal content "message")
+               (equal type "rfc822"))
           (notmuch-show--register-cids
            msg
            (car (plist-get (car (plist-get part :content)) :body)))))))
@@ -852,10 +846,9 @@ will return nil if the CID is unknown or cannot be retrieved."
              (push func result)))
          ;; Reverse order of prefrence.
          (list (intern (concat "notmuch-show-insert-part-*/*"))
-               (intern (concat
-                        "notmuch-show-insert-part-"
-                        (car (notmuch-split-content-type content-type))
-                        "/*"))
+               (intern (concat "notmuch-show-insert-part-"
+                               (car (split-string content-type "/"))
+                               "/*"))
                (intern (concat "notmuch-show-insert-part-" content-type))))
     result))
 
@@ -1485,7 +1478,6 @@ reset based on the original query."
     (define-key map "B" 'notmuch-show-browse-urls)
     map)
   "Keymap for \"notmuch show\" buffers.")
-(fset 'notmuch-show-mode-map notmuch-show-mode-map)
 
 (define-derived-mode notmuch-show-mode fundamental-mode "notmuch-show"
   "Major mode for viewing a thread with notmuch.
@@ -1759,7 +1751,7 @@ user decision and we should not override it."
          (funcall notmuch-show-mark-read-function (window-start) (window-end))
        ((debug error)
         (unless notmuch-show--seen-has-errored
-          (setq notmuch-show--seen-has-errored 't)
+          (setq notmuch-show--seen-has-errored t)
           (setq header-line-format
                 (concat header-line-format
                         (propertize
@@ -2349,7 +2341,9 @@ the user (see `notmuch-show-stash-mlarchive-link-alist')."
   (browse-url (current-kill 0 t)))
 
 (defun notmuch-show-stash-git-helper (addresses prefix)
-  "Escape, trim, quote, and add PREFIX to each address in list of ADDRESSES, and return the result as a single string."
+  "Normalize all ADDRESSES while adding PREFIX.
+Escape, trim, quote and add PREFIX to each address in list
+of ADDRESSES, and return the result as a single string."
   (mapconcat (lambda (x)
               (concat prefix "\""
                       ;; escape double-quotes
@@ -2362,10 +2356,12 @@ the user (see `notmuch-show-stash-mlarchive-link-alist')."
             addresses " "))
 
 (put 'notmuch-show-stash-git-send-email 'notmuch-prefix-doc
-     "Copy From/To/Cc of current message to kill-ring in a form suitable for pasting to git send-email command line.")
+     "Copy From/To/Cc of current message to kill-ring.
+Use a form suitable for pasting to git send-email command line.")
 
 (defun notmuch-show-stash-git-send-email (&optional no-in-reply-to)
-  "Copy From/To/Cc/Message-Id of current message to kill-ring in a form suitable for pasting to git send-email command line.
+  "Copy From/To/Cc/Message-Id of current message to kill-ring.
+Use a form suitable for pasting to git send-email command line.
 
 If invoked with a prefix argument (or NO-IN-REPLY-TO is non-nil),
 omit --in-reply-to=<Message-Id>."
@@ -2418,10 +2414,9 @@ This ensures that the temporary buffer created for the mm-handle
 is destroyed when FN returns. If MIME-TYPE is given then force
 part to be treated as if it had that mime-type."
   (let ((handle (notmuch-show-current-part-handle mime-type)))
-    ;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
-    ;; call it from a temp-buffer, unless
-    ;; notmuch-show-attachment-debug is non-nil in which case we put
-    ;; it in " *notmuch-part*".
+    ;; Emacs puts stdout/stderr into the calling buffer so we call
+    ;; it from a temp-buffer, unless notmuch-show-attachment-debug
+    ;; is non-nil, in which case we put it in " *notmuch-part*".
     (unwind-protect
        (if notmuch-show-attachment-debug
            (with-current-buffer (generate-new-buffer " *notmuch-part*")
index 5d4a6865c4c4ffd7aa98be764e0ada386fef4f02..925de78c7dc6bc4927d60cc31ead75e6a1e52211 100644 (file)
@@ -68,15 +68,15 @@ The key `notmuch-tag-jump-reverse-key' (k by default) should not
 be used (either as a key, or as the start of a key sequence) as
 it is already bound: it switches the menu to a menu of the
 reverse tagging operations. The reverse of a tagging operation is
-the same list of individual tag-ops but with `+tag` replaced by
-`-tag` and vice versa.
+the same list of individual tag-ops but with `+tag' replaced by
+`-tag' and vice versa.
 
 If setting this variable outside of customize then it should be a
 list of triples (lists of three elements). Each triple should be
 of the form (key-binding tagging-operations name). KEY-BINDING
 can be a single character or a key sequence; TAGGING-OPERATIONS
 should either be a list of individual tag operations each of the
-form `+tag` or `-tag`, or the variable name of a variable that is
+form `+tag' or `-tag', or the variable name of a variable that is
 a list of tagging operations; NAME should be a name for the
 tagging operation, if omitted or empty than then name is taken
 from TAGGING-OPERATIONS."
@@ -116,7 +116,7 @@ from TAGGING-OPERATIONS."
   '((t :foreground "red"))
   "Default face used for the unread tag.
 
-Used in the default value of `notmuch-tag-formats`."
+Used in the default value of `notmuch-tag-formats'."
   :group 'notmuch-faces)
 
 (defface notmuch-tag-flagged
@@ -128,7 +128,7 @@ Used in the default value of `notmuch-tag-formats`."
      (:foreground "blue")))
   "Face used for the flagged tag.
 
-Used in the default value of `notmuch-tag-formats`."
+Used in the default value of `notmuch-tag-formats'."
   :group 'notmuch-faces)
 
 (defcustom notmuch-tag-formats
@@ -170,7 +170,7 @@ with images."
     (t :inverse-video t))
   "Face used to display deleted tags.
 
-Used in the default value of `notmuch-tag-deleted-formats`."
+Used in the default value of `notmuch-tag-deleted-formats'."
   :group 'notmuch-faces)
 
 (defcustom notmuch-tag-deleted-formats
@@ -178,7 +178,7 @@ Used in the default value of `notmuch-tag-deleted-formats`."
     (".*" (notmuch-apply-face tag `notmuch-tag-deleted)))
   "Custom formats for tags when deleted.
 
-For deleted tags the formats in `notmuch-tag-formats` are applied
+For deleted tags the formats in `notmuch-tag-formats' are applied
 first and then these formats are applied on top; that is `tag'
 passed to the function is the tag with all these previous
 formattings applied. The formatted can access the original
@@ -199,14 +199,14 @@ See `notmuch-tag-formats' for full documentation."
   '((t :underline "green"))
   "Default face used for added tags.
 
-Used in the default value for `notmuch-tag-added-formats`."
+Used in the default value for `notmuch-tag-added-formats'."
   :group 'notmuch-faces)
 
 (defcustom notmuch-tag-added-formats
   '((".*" (notmuch-apply-face tag 'notmuch-tag-added)))
   "Custom formats for tags when added.
 
-For added tags the formats in `notmuch-tag-formats` are applied
+For added tags the formats in `notmuch-tag-formats' are applied
 first and then these formats are applied on top.
 
 To disable special formatting of added tags, set this variable to
@@ -506,7 +506,7 @@ begin with a \"+\" or a \"-\". If REVERSE is non-nil, replace all
 Creates and displays a jump menu for the tagging operations
 specified in `notmuch-tagging-keys'. If REVERSE is set then it
 offers a menu of the reverses of the operations specified in
-`notmuch-tagging-keys'; i.e. each `+tag` is replaced by `-tag`
+`notmuch-tagging-keys'; i.e. each `+tag' is replaced by `-tag'
 and vice versa."
   ;; In principle this function is simple, but it has to deal with
   ;; lots of cases: different modes (search/show/tree), whether a name
@@ -524,7 +524,7 @@ and vice versa."
                      (symbol-value tag)
                    tag))
             (tag-change (if reverse
-                            (notmuch-tag-change-list tag 't)
+                            (notmuch-tag-change-list tag t)
                           tag))
             (name (or (and (not (string= name ""))
                            name)
index f342f85aadd89bc5c9300426629c35124fa67f2c..57843e256f437d5f6206ef31e5aab9915d10e49a 100644 (file)
 (declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
 (declare-function notmuch-search-find-subject "notmuch" ())
 
+;; For `notmuch-tree-next-thread-from-search'.
+(declare-function notmuch-search-next-thread "notmuch" ())
+(declare-function notmuch-search-previous-thread "notmuch" ())
+(declare-function notmuch-tree-from-search-thread "notmuch" ())
+
 ;; the following variable is defined in notmuch.el
 (defvar notmuch-search-query-string)
 
 ;; this variable distinguishes the unthreaded display from the normal tree display
-(defvar notmuch-tree-unthreaded nil
+(defvar-local notmuch-tree-unthreaded nil
   "A buffer local copy of argument unthreaded to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-unthreaded)
 
 (defgroup notmuch-tree nil
   "Showing message and thread structure."
@@ -202,105 +206,115 @@ For example:
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
-(defvar notmuch-tree-previous-subject
+(defvar-local notmuch-tree-previous-subject
   "The subject of the most recent result shown during the async display.")
-(make-variable-buffer-local 'notmuch-tree-previous-subject)
 
-(defvar notmuch-tree-basic-query nil
+(defvar-local notmuch-tree-basic-query nil
   "A buffer local copy of argument query to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-basic-query)
 
-(defvar notmuch-tree-query-context nil
+(defvar-local notmuch-tree-query-context nil
   "A buffer local copy of argument query-context to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-query-context)
 
-(defvar notmuch-tree-target-msg nil
+(defvar-local notmuch-tree-target-msg nil
   "A buffer local copy of argument target to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-target-msg)
 
-(defvar notmuch-tree-open-target nil
+(defvar-local notmuch-tree-open-target nil
   "A buffer local copy of argument open-target to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-open-target)
 
-(defvar notmuch-tree-parent-buffer nil)
-(make-variable-buffer-local 'notmuch-tree-parent-buffer)
+(defvar-local notmuch-tree-parent-buffer nil)
 
-(defvar notmuch-tree-message-window nil
+(defvar-local notmuch-tree-message-window nil
   "The window of the message pane.
 
 It is set in both the tree buffer and the child show buffer. It
 is used to try and close the message pane when quitting tree view
 or the child show buffer.")
-(make-variable-buffer-local 'notmuch-tree-message-window)
 (put 'notmuch-tree-message-window 'permanent-local t)
 
-(defvar notmuch-tree-message-buffer nil
+(defvar-local notmuch-tree-message-buffer nil
   "The buffer name of the show buffer in the message pane.
 
 This is used to try and make sure we don't close the message pane
 if the user has loaded a different buffer in that window.")
-(make-variable-buffer-local 'notmuch-tree-message-buffer)
 (put 'notmuch-tree-message-buffer 'permanent-local t)
 
-(defun notmuch-tree-to-message-pane (func)
-  "Execute FUNC in message pane.
-
-This function returns a function (so can be used as a keybinding)
-which executes function FUNC in the message pane if it is
-open (if the message pane is closed it does nothing)."
-  `(lambda ()
-     ,(concat "(In message pane) " (documentation func t))
+(defmacro notmuch-tree--define-do-in-message-window (name cmd)
+  "Define NAME as a command that calls CMD interactively in the message window.
+If the message pane is closed then this command does nothing.
+Avoid using this macro in new code; it will be removed."
+  `(defun ,name ()
+     ,(concat "(In message window) " (documentation cmd t))
      (interactive)
      (when (window-live-p notmuch-tree-message-window)
        (with-selected-window notmuch-tree-message-window
-        (call-interactively #',func)))))
-
-(defun notmuch-tree-inherit-from-message-pane (sym)
-  "Return value of SYM in message-pane if open, or tree-pane if not."
+        (call-interactively #',cmd)))))
+
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-previous-message-button
+ notmuch-show-previous-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-next-message-button
+ notmuch-show-next-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-toggle-message-process-crypto
+ notmuch-show-toggle-process-crypto)
+
+(defun notmuch-tree--message-process-crypto ()
+  "Return value of `notmuch-show-process-crypto' in the message window.
+If that window isn't alive, then return the current value.
+Avoid using this function in new code; it will be removed."
   (if (window-live-p notmuch-tree-message-window)
       (with-selected-window notmuch-tree-message-window
-       (symbol-value sym))
-    (symbol-value sym)))
-
-(defun notmuch-tree-button-activate (&optional button)
-  "Activate BUTTON or button at point.
-
-This function does not give an error if there is no button."
-  (interactive)
-  (let ((button (or button (button-at (point)))))
-    (when button (button-activate button))))
-
-(defun notmuch-tree-close-message-pane-and (func)
-  "Close message pane and execute FUNC.
-
-This function returns a function (so can be used as a keybinding)
-which closes the message pane if open and then executes function
-FUNC."
-  `(lambda ()
-     ,(concat "(Close message pane and) " (documentation func t))
+       notmuch-show-process-crypto)
+    notmuch-show-process-crypto))
+
+(defmacro notmuch-tree--define-close-message-window-and (name cmd)
+  "Define NAME as a variant of CMD.
+
+NAME determines the value of `notmuch-show-process-crypto' in the
+message window, closes the window, and then call CMD interactively
+with that value let-bound.  If the message window does not exist,
+then NAME behaves like CMD."
+  `(defun ,name ()
+     ,(concat "(Close message pane and) " (documentation cmd t))
      (interactive)
      (let ((notmuch-show-process-crypto
-           (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
+           (notmuch-tree--message-process-crypto)))
        (notmuch-tree-close-message-window)
-       (call-interactively #',func))))
+       (call-interactively #',cmd))))
+
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-help
+ notmuch-help)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-new-mail
+ notmuch-mua-new-mail)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-jump-search
+ notmuch-jump-search)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-forward-message
+ notmuch-show-forward-message)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply-sender
+ notmuch-show-reply-sender)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply
+ notmuch-show-reply)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-view-raw-message
+ notmuch-show-view-raw-message)
 
 (defvar notmuch-tree-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
-    ;; The following override the global keymap.
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-help]
-      (notmuch-tree-close-message-pane-and #'notmuch-help))
-    ;; Override because we first close message pane and then close tree buffer.
+    ;; These bindings shadow common bindings with variants
+    ;; that additionally close the message window.
     (define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit)
-    ;; Override because we close message pane after the search query is entered.
-    (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-mua-new-mail]
-      (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-jump-search]
-      (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
+    (define-key map [remap notmuch-search]       'notmuch-tree-to-search)
+    (define-key map [remap notmuch-help]         'notmuch-tree-help)
+    (define-key map [remap notmuch-mua-new-mail] 'notmuch-tree-new-mail)
+    (define-key map [remap notmuch-jump-search]  'notmuch-tree-jump-search)
 
     (define-key map "S" 'notmuch-search-from-tree-current-query)
     (define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
@@ -314,24 +328,16 @@ FUNC."
     (define-key map "b" 'notmuch-show-resend-message)
 
     ;; these apply to the message pane
-    (define-key map (kbd "M-TAB")
-      (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "<backtab>")
-      (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "TAB")
-      (notmuch-tree-to-message-pane #'notmuch-show-next-button))
-    (define-key map "$"
-      (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
+    (define-key map (kbd "M-TAB")     'notmuch-tree-previous-message-button)
+    (define-key map (kbd "<backtab>") 'notmuch-tree-previous-message-button)
+    (define-key map (kbd "TAB")       'notmuch-tree-next-message-button)
+    (define-key map "$" 'notmuch-tree-toggle-message-process-crypto)
 
     ;; bindings from show (or elsewhere) but we close the message pane first.
-    (define-key map "f"
-      (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
-    (define-key map "r"
-      (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
-    (define-key map "R"
-      (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
-    (define-key map "V"
-      (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+    (define-key map "f" 'notmuch-tree-forward-message)
+    (define-key map "r" 'notmuch-tree-reply-sender)
+    (define-key map "R" 'notmuch-tree-reply)
+    (define-key map "V" 'notmuch-tree-view-raw-message)
 
     ;; The main tree view bindings
     (define-key map (kbd "RET") 'notmuch-tree-show-message)
@@ -354,8 +360,8 @@ FUNC."
     (define-key map " " 'notmuch-tree-scroll-or-next)
     (define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back)
     (define-key map "e" 'notmuch-tree-resume-message)
-    map))
-(fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
+    map)
+  "Keymap for \"notmuch tree\" buffers.")
 
 (defun notmuch-tree-get-message-properties ()
   "Return the properties of the current message as a plist.
@@ -526,9 +532,10 @@ NOT change the database."
   (let ((buffer (current-buffer)))
     (when (and (window-live-p notmuch-tree-message-window)
               (eq (window-buffer notmuch-tree-message-window) buffer))
-      ;; We do not want an error if this is the sole window in the
-      ;; frame and I do not know how to test for that in emacs pre
-      ;; 24. Hence we just ignore-errors.
+      ;; We could check whether this is the only window in its frame,
+      ;; but simply ignoring the error that is thrown otherwise is
+      ;; what we had to do for Emacs 24 and we stick to that because
+      ;; it is still the simplest approach.
       (ignore-errors
        (delete-window notmuch-tree-message-window)))))
 
index 83bcee57c675bce1959275315a782c4319c9622a..95770fc3034e067ca4ec2cf39773b14be9527b3b 100644 (file)
@@ -194,7 +194,6 @@ there will be called at other points of notmuch execution."
     (define-key map "U" 'notmuch-unthreaded-from-search-current-query)
     map)
   "Keymap for \"notmuch search\" buffers.")
-(fset 'notmuch-search-mode-map notmuch-search-mode-map)
 
 (defvar notmuch-search-stash-map
   (let ((map (make-sparse-keymap)))
@@ -343,7 +342,7 @@ there will be called at other points of notmuch execution."
   "Face used in search mode face for flagged threads.
 
 This face is the default value for the \"flagged\" tag in
-`notmuch-search-line-faces`."
+`notmuch-search-line-faces'."
   :group 'notmuch-search
   :group 'notmuch-faces)
 
@@ -353,7 +352,7 @@ This face is the default value for the \"flagged\" tag in
   "Face used in search mode for unread threads.
 
 This face is the default value for the \"unread\" tag in
-`notmuch-search-line-faces`."
+`notmuch-search-line-faces'."
   :group 'notmuch-search
   :group 'notmuch-faces)
 
@@ -392,7 +391,7 @@ Complete list of currently available key bindings:
   (make-local-variable 'notmuch-search-target-thread)
   (make-local-variable 'notmuch-search-target-line)
   (setq notmuch-buffer-refresh-function #'notmuch-search-refresh-view)
-  (set (make-local-variable 'scroll-preserve-screen-position) t)
+  (setq-local scroll-preserve-screen-position t)
   (add-to-invisibility-spec (cons 'ellipsis t))
   (setq truncate-lines t)
   (setq buffer-read-only t)
index a640012676d818a42490ebb93359121710f13aa0..01cbb3f2821c8e0a1e0145192e2e3368b625d6c7 100644 (file)
@@ -59,7 +59,10 @@ libnotmuch_cxx_srcs =                \
        $(dir)/config.cc        \
        $(dir)/regexp-fields.cc \
        $(dir)/thread.cc \
-       $(dir)/thread-fp.cc
+       $(dir)/thread-fp.cc     \
+       $(dir)/features.cc      \
+       $(dir)/prefix.cc        \
+       $(dir)/open.cc
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
 
index 041602cdc6a8911b33531cfa8e0dbd72307b8242..c9bc712b46c1a0b51c8d93af9189111604b7dd7f 100644 (file)
@@ -32,6 +32,8 @@
 
 #include "notmuch-private.h"
 
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
 #ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
 #define XAPIAN_DEPRECATED(D) D
 #endif
@@ -263,4 +265,23 @@ _notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
                                const char *value,
                                Xapian::PostingIterator *begin,
                                Xapian::PostingIterator *end);
+
+#define NOTMUCH_DATABASE_VERSION 3
+
+/* features.cc */
+
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+                                 char mode, char **incompat_out);
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features);
+
+/* prefix.cc */
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch);
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
+
 #endif
index 7518968599f65fbeed8ac5285e15fb3cfb1cdc2e..0f4e2ff96d4d6b9e7ed731d320219aecbd93d1ec 100644 (file)
  */
 
 #include "database-private.h"
-#include "parse-time-vrp.h"
-#include "query-fp.h"
-#include "thread-fp.h"
-#include "regexp-fields.h"
 #include "string-util.h"
 
 #include <iostream>
@@ -39,8 +35,6 @@
 
 using namespace std;
 
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
 typedef struct {
     const char *name;
     const char *prefix;
@@ -52,12 +46,6 @@ typedef struct {
 #define STRINGIFY(s) _SUB_STRINGIFY (s)
 #define _SUB_STRINGIFY(s) #s
 
-#if HAVE_XAPIAN_DB_RETRY_LOCK
-#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
-#else
-#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
-#endif
-
 #define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
 
 static void
@@ -265,80 +253,6 @@ _notmuch_database_mode (notmuch_database_t *notmuch)
  *                     same thread.
  */
 
-/* With these prefix values we follow the conventions published here:
- *
- * https://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-static const
-prefix_t prefix_table[] = {
-    /* name                    term prefix     flags */
-    { "type",                   "T",            NOTMUCH_FIELD_NO_FLAGS },
-    { "reference",              "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
-    { "replyto",                "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
-    { "directory",              "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "file-direntry",          "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "directory-direntry",     "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "body",                   "",             NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC },
-    { "thread",                 "G",            NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "tag",                    "K",            NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "is",                     "K",            NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "id",                     "Q",            NOTMUCH_FIELD_EXTERNAL },
-    { "mid",                    "Q",            NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "path",                   "P",            NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "property",               "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
-    /*
-     * Unconditionally add ':' to reduce potential ambiguity with
-     * overlapping prefixes and/or terms that start with capital
-     * letters. See Xapian document termprefixes.html for related
-     * discussion.
-     */
-    { "folder",                 "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "from",                   "XFROM",        NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "to",                     "XTO",          NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC },
-    { "attachment",             "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC },
-    { "mimetype",               "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC },
-    { "subject",                "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC |
-      NOTMUCH_FIELD_PROCESSOR },
-};
-
-static void
-_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    if (prefix->prefix)
-       notmuch->query_parser->add_prefix ("", prefix->prefix);
-    if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
-       notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
-    else
-       notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
-}
 
 notmuch_string_map_iterator_t *
 _notmuch_database_user_headers (notmuch_database_t *notmuch)
@@ -346,153 +260,6 @@ _notmuch_database_user_headers (notmuch_database_t *notmuch)
     return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
 }
 
-const char *
-_user_prefix (void *ctx, const char *name)
-{
-    return talloc_asprintf (ctx, "XU%s:", name);
-}
-
-static notmuch_status_t
-_setup_user_query_fields (notmuch_database_t *notmuch)
-{
-    notmuch_config_list_t *list;
-    notmuch_status_t status;
-
-    notmuch->user_prefix = _notmuch_string_map_create (notmuch);
-    if (notmuch->user_prefix == NULL)
-       return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
-    notmuch->user_header = _notmuch_string_map_create (notmuch);
-    if (notmuch->user_header == NULL)
-       return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
-    status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
-    if (status)
-       return status;
-
-    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-
-       prefix_t query_field;
-
-       const char *key = notmuch_config_list_key (list)
-                         + sizeof (CONFIG_HEADER_PREFIX) - 1;
-
-       _notmuch_string_map_append (notmuch->user_prefix,
-                                   key,
-                                   _user_prefix (notmuch, key));
-
-       _notmuch_string_map_append (notmuch->user_header,
-                                   key,
-                                   notmuch_config_list_value (list));
-
-       query_field.name = talloc_strdup (notmuch, key);
-       query_field.prefix = _user_prefix (notmuch, key);
-       query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
-                           | NOTMUCH_FIELD_EXTERNAL;
-
-       _setup_query_field_default (&query_field, notmuch);
-    }
-
-    notmuch_config_list_destroy (list);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-static void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
-       Xapian::FieldProcessor *fp;
-
-       if (STRNCMP_LITERAL (prefix->name, "date") == 0)
-           fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
-       else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
-           fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
-       else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
-           fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
-       else
-           fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
-                                           *notmuch->query_parser, notmuch))->release ();
-
-       /* we treat all field-processor fields as boolean in order to get the raw input */
-       if (prefix->prefix)
-           notmuch->query_parser->add_prefix ("", prefix->prefix);
-       notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
-    } else {
-       _setup_query_field_default (prefix, notmuch);
-    }
-}
-
-const char *
-_find_prefix (const char *name)
-{
-    unsigned int i;
-
-    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-       if (strcmp (name, prefix_table[i].name) == 0)
-           return prefix_table[i].prefix;
-    }
-
-    INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
-    return "";
-}
-
-/* Like find prefix, but include the possibility of user defined
- * prefixes specific to this database */
-
-const char *
-_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
-{
-    unsigned int i;
-
-    /*XXX TODO: reduce code duplication */
-    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-       if (strcmp (name, prefix_table[i].name) == 0)
-           return prefix_table[i].prefix;
-    }
-
-    if (notmuch->user_prefix)
-       return _notmuch_string_map_get (notmuch->user_prefix, name);
-
-    return NULL;
-}
-
-static const struct {
-    /* NOTMUCH_FEATURE_* value. */
-    _notmuch_features value;
-    /* Feature name as it appears in the database.  This name should
-     * be appropriate for displaying to the user if an older version
-     * of notmuch doesn't support this feature. */
-    const char *name;
-    /* Compatibility flags when this feature is declared. */
-    const char *flags;
-} feature_names[] = {
-    { NOTMUCH_FEATURE_FILE_TERMS,
-      "multiple paths per message", "rw" },
-    { NOTMUCH_FEATURE_DIRECTORY_DOCS,
-      "relative directory paths", "rw" },
-    /* Header values are not required for reading a database because a
-     * reader can just refer to the message file. */
-    { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
-      "from/subject/message-ID in database", "w" },
-    { NOTMUCH_FEATURE_BOOL_FOLDER,
-      "exact folder:/path: search", "rw" },
-    { NOTMUCH_FEATURE_GHOSTS,
-      "mail documents for missing messages", "w" },
-    /* Knowledge of the index mime-types are not required for reading
-     * a database because a reader will just be unable to query
-     * them. */
-    { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
-      "indexed MIME types", "w" },
-    { NOTMUCH_FEATURE_LAST_MOD,
-      "modification tracking", "w" },
-    /* Existing databases will work fine for all queries not involving
-     * 'body:' */
-    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
-      "index body and headers separately", "w" },
-};
-
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -817,291 +584,6 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch)
     return new_revision;
 }
 
-/* Parse a database features string from the given database version.
- * Returns the feature bit set.
- *
- * For version < 3, this ignores the features string and returns a
- * hard-coded set of features.
- *
- * If there are unrecognized features that are required to open the
- * database in mode (which should be 'r' or 'w'), return a
- * comma-separated list of unrecognized but required features in
- * *incompat_out suitable for presenting to the user.  *incompat_out
- * will be allocated from ctx.
- */
-static _notmuch_features
-_parse_features (const void *ctx, const char *features, unsigned int version,
-                char mode, char **incompat_out)
-{
-    _notmuch_features res = static_cast<_notmuch_features>(0);
-    unsigned int namelen, i;
-    size_t llen = 0;
-    const char *flags;
-
-    /* Prior to database version 3, features were implied by the
-     * version number. */
-    if (version == 0)
-       return NOTMUCH_FEATURES_V0;
-    else if (version == 1)
-       return NOTMUCH_FEATURES_V1;
-    else if (version == 2)
-       return NOTMUCH_FEATURES_V2;
-
-    /* Parse the features string */
-    while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
-       flags = strchr (features, '\t');
-       if (! flags || flags > features + llen)
-           continue;
-       namelen = flags - features;
-
-       for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
-           if (strlen (feature_names[i].name) == namelen &&
-               strncmp (feature_names[i].name, features, namelen) == 0) {
-               res |= feature_names[i].value;
-               break;
-           }
-       }
-
-       if (i == ARRAY_SIZE (feature_names) && incompat_out) {
-           /* Unrecognized feature */
-           const char *have = strchr (flags, mode);
-           if (have && have < features + llen) {
-               /* This feature is required to access this database in
-                * 'mode', but we don't understand it. */
-               if (! *incompat_out)
-                   *incompat_out = talloc_strdup (ctx, "");
-               *incompat_out = talloc_asprintf_append_buffer (
-                   *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
-                   namelen, features);
-           }
-       }
-    }
-
-    return res;
-}
-
-static char *
-_print_features (const void *ctx, unsigned int features)
-{
-    unsigned int i;
-    char *res = talloc_strdup (ctx, "");
-
-    for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
-       if (features & feature_names[i].value)
-           res = talloc_asprintf_append_buffer (
-               res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
-
-    return res;
-}
-
-notmuch_status_t
-notmuch_database_open (const char *path,
-                      notmuch_database_mode_t mode,
-                      notmuch_database_t **database)
-{
-    char *status_string = NULL;
-    notmuch_status_t status;
-
-    status = notmuch_database_open_verbose (path, mode, database,
-                                           &status_string);
-
-    if (status_string) {
-       fputs (status_string, stderr);
-       free (status_string);
-    }
-
-    return status;
-}
-
-notmuch_status_t
-notmuch_database_open_verbose (const char *path,
-                              notmuch_database_mode_t mode,
-                              notmuch_database_t **database,
-                              char **status_string)
-{
-    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
-    void *local = talloc_new (NULL);
-    notmuch_database_t *notmuch = NULL;
-    char *notmuch_path, *xapian_path, *incompat_features;
-    char *message = NULL;
-    struct stat st;
-    int err;
-    unsigned int i, version;
-    static int initialized = 0;
-
-    if (path == NULL) {
-       message = strdup ("Error: Cannot open a database for a NULL path.\n");
-       status = NOTMUCH_STATUS_NULL_POINTER;
-       goto DONE;
-    }
-
-    if (path[0] != '/') {
-       message = strdup ("Error: Database path must be absolute.\n");
-       status = NOTMUCH_STATUS_PATH_ERROR;
-       goto DONE;
-    }
-
-    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
-       message = strdup ("Out of memory\n");
-       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
-       goto DONE;
-    }
-
-    err = stat (notmuch_path, &st);
-    if (err) {
-       IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
-                                notmuch_path, strerror (errno)));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
-       message = strdup ("Out of memory\n");
-       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
-       goto DONE;
-    }
-
-    /* Initialize the GLib type system and threads */
-#if ! GLIB_CHECK_VERSION (2, 35, 1)
-    g_type_init ();
-#endif
-
-    /* Initialize gmime */
-    if (! initialized) {
-       g_mime_init ();
-       initialized = 1;
-    }
-
-    notmuch = talloc_zero (NULL, notmuch_database_t);
-    notmuch->exception_reported = false;
-    notmuch->status_string = NULL;
-    notmuch->path = talloc_strdup (notmuch, path);
-
-    strip_trailing (notmuch->path, '/');
-
-    notmuch->writable_xapian_db = NULL;
-    notmuch->atomic_nesting = 0;
-    notmuch->view = 1;
-    try {
-       string last_thread_id;
-       string last_mod;
-
-       if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
-           notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
-                                                                       DB_ACTION);
-           notmuch->xapian_db = notmuch->writable_xapian_db;
-       } else {
-           notmuch->xapian_db = new Xapian::Database (xapian_path);
-       }
-
-       /* Check version.  As of database version 3, we represent
-        * changes in terms of features, so assume a version bump
-        * means a dramatically incompatible change. */
-       version = notmuch_database_get_version (notmuch);
-       if (version > NOTMUCH_DATABASE_VERSION) {
-           IGNORE_RESULT (asprintf (&message,
-                                    "Error: Notmuch database at %s\n"
-                                    "       has a newer database format version (%u) than supported by this\n"
-                                    "       version of notmuch (%u).\n",
-                                    notmuch_path, version, NOTMUCH_DATABASE_VERSION));
-           notmuch_database_destroy (notmuch);
-           notmuch = NULL;
-           status = NOTMUCH_STATUS_FILE_ERROR;
-           goto DONE;
-       }
-
-       /* Check features. */
-       incompat_features = NULL;
-       notmuch->features = _parse_features (
-           local, notmuch->xapian_db->get_metadata ("features").c_str (),
-           version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
-           &incompat_features);
-       if (incompat_features) {
-           IGNORE_RESULT (asprintf (&message,
-                                    "Error: Notmuch database at %s\n"
-                                    "       requires features (%s)\n"
-                                    "       not supported by this version of notmuch.\n",
-                                    notmuch_path, incompat_features));
-           notmuch_database_destroy (notmuch);
-           notmuch = NULL;
-           status = NOTMUCH_STATUS_FILE_ERROR;
-           goto DONE;
-       }
-
-       notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
-       last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
-       if (last_thread_id.empty ()) {
-           notmuch->last_thread_id = 0;
-       } else {
-           const char *str;
-           char *end;
-
-           str = last_thread_id.c_str ();
-           notmuch->last_thread_id = strtoull (str, &end, 16);
-           if (*end != '\0')
-               INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
-       }
-
-       /* Get current highest revision number. */
-       last_mod = notmuch->xapian_db->get_value_upper_bound (
-           NOTMUCH_VALUE_LAST_MOD);
-       if (last_mod.empty ())
-           notmuch->revision = 0;
-       else
-           notmuch->revision = Xapian::sortable_unserialise (last_mod);
-       notmuch->uuid = talloc_strdup (
-           notmuch, notmuch->xapian_db->get_uuid ().c_str ());
-
-       notmuch->query_parser = new Xapian::QueryParser;
-       notmuch->term_gen = new Xapian::TermGenerator;
-       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
-       notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-       notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
-       notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
-       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
-       notmuch->query_parser->set_database (*notmuch->xapian_db);
-       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
-       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
-       notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
-       notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
-       notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
-
-       for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-           const prefix_t *prefix = &prefix_table[i];
-           if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
-               _setup_query_field (prefix, notmuch);
-           }
-       }
-       status = _setup_user_query_fields (notmuch);
-    } catch (const Xapian::Error &error) {
-       IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
-                                error.get_msg ().c_str ()));
-       notmuch_database_destroy (notmuch);
-       notmuch = NULL;
-       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-    }
-
-  DONE:
-    talloc_free (local);
-
-    if (message) {
-       if (status_string)
-           *status_string = message;
-       else
-           free (message);
-    }
-
-    if (database)
-       *database = notmuch;
-    else
-       talloc_free (notmuch);
-
-    if (notmuch)
-       notmuch->open = true;
-
-    return status;
-}
-
 notmuch_status_t
 notmuch_database_close (notmuch_database_t *notmuch)
 {
@@ -1674,7 +1156,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     }
 
     status = NOTMUCH_STATUS_SUCCESS;
-    db->set_metadata ("features", _print_features (local, notmuch->features));
+    db->set_metadata ("features", _notmuch_database_print_features (local, notmuch->features));
     db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
 
   DONE:
diff --git a/lib/features.cc b/lib/features.cc
new file mode 100644 (file)
index 0000000..8def246
--- /dev/null
@@ -0,0 +1,114 @@
+#include "database-private.h"
+
+static const struct {
+    /* NOTMUCH_FEATURE_* value. */
+    _notmuch_features value;
+    /* Feature name as it appears in the database.  This name should
+     * be appropriate for displaying to the user if an older version
+     * of notmuch doesn't support this feature. */
+    const char *name;
+    /* Compatibility flags when this feature is declared. */
+    const char *flags;
+} feature_names[] = {
+    { NOTMUCH_FEATURE_FILE_TERMS,
+      "multiple paths per message", "rw" },
+    { NOTMUCH_FEATURE_DIRECTORY_DOCS,
+      "relative directory paths", "rw" },
+    /* Header values are not required for reading a database because a
+     * reader can just refer to the message file. */
+    { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
+      "from/subject/message-ID in database", "w" },
+    { NOTMUCH_FEATURE_BOOL_FOLDER,
+      "exact folder:/path: search", "rw" },
+    { NOTMUCH_FEATURE_GHOSTS,
+      "mail documents for missing messages", "w" },
+    /* Knowledge of the index mime-types are not required for reading
+     * a database because a reader will just be unable to query
+     * them. */
+    { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
+      "indexed MIME types", "w" },
+    { NOTMUCH_FEATURE_LAST_MOD,
+      "modification tracking", "w" },
+    /* Existing databases will work fine for all queries not involving
+     * 'body:' */
+    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
+      "index body and headers separately", "w" },
+};
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features)
+{
+    unsigned int i;
+    char *res = talloc_strdup (ctx, "");
+
+    for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
+       if (features & feature_names[i].value)
+           res = talloc_asprintf_append_buffer (
+               res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
+
+    return res;
+}
+
+
+/* Parse a database features string from the given database version.
+ * Returns the feature bit set.
+ *
+ * For version < 3, this ignores the features string and returns a
+ * hard-coded set of features.
+ *
+ * If there are unrecognized features that are required to open the
+ * database in mode (which should be 'r' or 'w'), return a
+ * comma-separated list of unrecognized but required features in
+ * *incompat_out suitable for presenting to the user.  *incompat_out
+ * will be allocated from ctx.
+ */
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+                char mode, char **incompat_out)
+{
+    _notmuch_features res = static_cast<_notmuch_features>(0);
+    unsigned int namelen, i;
+    size_t llen = 0;
+    const char *flags;
+
+    /* Prior to database version 3, features were implied by the
+     * version number. */
+    if (version == 0)
+       return NOTMUCH_FEATURES_V0;
+    else if (version == 1)
+       return NOTMUCH_FEATURES_V1;
+    else if (version == 2)
+       return NOTMUCH_FEATURES_V2;
+
+    /* Parse the features string */
+    while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
+       flags = strchr (features, '\t');
+       if (! flags || flags > features + llen)
+           continue;
+       namelen = flags - features;
+
+       for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
+           if (strlen (feature_names[i].name) == namelen &&
+               strncmp (feature_names[i].name, features, namelen) == 0) {
+               res |= feature_names[i].value;
+               break;
+           }
+       }
+
+       if (i == ARRAY_SIZE (feature_names) && incompat_out) {
+           /* Unrecognized feature */
+           const char *have = strchr (flags, mode);
+           if (have && have < features + llen) {
+               /* This feature is required to access this database in
+                * 'mode', but we don't understand it. */
+               if (! *incompat_out)
+                   *incompat_out = talloc_strdup (ctx, "");
+               *incompat_out = talloc_asprintf_append_buffer (
+                   *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
+                   namelen, features);
+           }
+       }
+    }
+
+    return res;
+}
diff --git a/lib/open.cc b/lib/open.cc
new file mode 100644 (file)
index 0000000..1b17e63
--- /dev/null
@@ -0,0 +1,218 @@
+#include <unistd.h>
+#include "database-private.h"
+#include "parse-time-vrp.h"
+
+#if HAVE_XAPIAN_DB_RETRY_LOCK
+#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
+#else
+#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
+#endif
+
+notmuch_status_t
+notmuch_database_open (const char *path,
+                      notmuch_database_mode_t mode,
+                      notmuch_database_t **database)
+{
+    char *status_string = NULL;
+    notmuch_status_t status;
+
+    status = notmuch_database_open_verbose (path, mode, database,
+                                           &status_string);
+
+    if (status_string) {
+       fputs (status_string, stderr);
+       free (status_string);
+    }
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_open_verbose (const char *path,
+                              notmuch_database_mode_t mode,
+                              notmuch_database_t **database,
+                              char **status_string)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    void *local = talloc_new (NULL);
+    notmuch_database_t *notmuch = NULL;
+    char *notmuch_path, *xapian_path, *incompat_features;
+    char *message = NULL;
+    struct stat st;
+    int err;
+    unsigned int version;
+    static int initialized = 0;
+
+    if (path == NULL) {
+       message = strdup ("Error: Cannot open a database for a NULL path.\n");
+       status = NOTMUCH_STATUS_NULL_POINTER;
+       goto DONE;
+    }
+
+    if (path[0] != '/') {
+       message = strdup ("Error: Database path must be absolute.\n");
+       status = NOTMUCH_STATUS_PATH_ERROR;
+       goto DONE;
+    }
+
+    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
+       message = strdup ("Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    err = stat (notmuch_path, &st);
+    if (err) {
+       IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
+                                notmuch_path, strerror (errno)));
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
+       message = strdup ("Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    /* Initialize the GLib type system and threads */
+#if ! GLIB_CHECK_VERSION (2, 35, 1)
+    g_type_init ();
+#endif
+
+    /* Initialize gmime */
+    if (! initialized) {
+       g_mime_init ();
+       initialized = 1;
+    }
+
+    notmuch = talloc_zero (NULL, notmuch_database_t);
+    notmuch->exception_reported = false;
+    notmuch->status_string = NULL;
+    notmuch->path = talloc_strdup (notmuch, path);
+
+    strip_trailing (notmuch->path, '/');
+
+    notmuch->writable_xapian_db = NULL;
+    notmuch->atomic_nesting = 0;
+    notmuch->view = 1;
+    try {
+       std::string last_thread_id;
+       std::string last_mod;
+
+       if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
+           notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
+                                                                       DB_ACTION);
+           notmuch->xapian_db = notmuch->writable_xapian_db;
+       } else {
+           notmuch->xapian_db = new Xapian::Database (xapian_path);
+       }
+
+       /* Check version.  As of database version 3, we represent
+        * changes in terms of features, so assume a version bump
+        * means a dramatically incompatible change. */
+       version = notmuch_database_get_version (notmuch);
+       if (version > NOTMUCH_DATABASE_VERSION) {
+           IGNORE_RESULT (asprintf (&message,
+                                    "Error: Notmuch database at %s\n"
+                                    "       has a newer database format version (%u) than supported by this\n"
+                                    "       version of notmuch (%u).\n",
+                                    notmuch_path, version, NOTMUCH_DATABASE_VERSION));
+           notmuch_database_destroy (notmuch);
+           notmuch = NULL;
+           status = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       /* Check features. */
+       incompat_features = NULL;
+       notmuch->features = _notmuch_database_parse_features (
+           local, notmuch->xapian_db->get_metadata ("features").c_str (),
+           version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
+           &incompat_features);
+       if (incompat_features) {
+           IGNORE_RESULT (asprintf (&message,
+                                    "Error: Notmuch database at %s\n"
+                                    "       requires features (%s)\n"
+                                    "       not supported by this version of notmuch.\n",
+                                    notmuch_path, incompat_features));
+           notmuch_database_destroy (notmuch);
+           notmuch = NULL;
+           status = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
+       last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
+       if (last_thread_id.empty ()) {
+           notmuch->last_thread_id = 0;
+       } else {
+           const char *str;
+           char *end;
+
+           str = last_thread_id.c_str ();
+           notmuch->last_thread_id = strtoull (str, &end, 16);
+           if (*end != '\0')
+               INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
+       }
+
+       /* Get current highest revision number. */
+       last_mod = notmuch->xapian_db->get_value_upper_bound (
+           NOTMUCH_VALUE_LAST_MOD);
+       if (last_mod.empty ())
+           notmuch->revision = 0;
+       else
+           notmuch->revision = Xapian::sortable_unserialise (last_mod);
+       notmuch->uuid = talloc_strdup (
+           notmuch, notmuch->xapian_db->get_uuid ().c_str ());
+
+       notmuch->query_parser = new Xapian::QueryParser;
+       notmuch->term_gen = new Xapian::TermGenerator;
+       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+       notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+       notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
+       notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
+       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
+       notmuch->query_parser->set_database (*notmuch->xapian_db);
+       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+       notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
+       notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
+       notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
+
+       status = _notmuch_database_setup_standard_query_fields (notmuch);
+       if (status)
+           goto DONE;
+
+       status = _notmuch_database_setup_user_query_fields (notmuch);
+       if (status)
+           goto DONE;
+
+    } catch (const Xapian::Error &error) {
+       IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
+                                error.get_msg ().c_str ()));
+       notmuch_database_destroy (notmuch);
+       notmuch = NULL;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+  DONE:
+    talloc_free (local);
+
+    if (message) {
+       if (status_string)
+           *status_string = message;
+       else
+           free (message);
+    }
+
+    if (database)
+       *database = notmuch;
+    else
+       talloc_free (notmuch);
+
+    if (notmuch)
+       notmuch->open = true;
+
+    return status;
+}
diff --git a/lib/prefix.cc b/lib/prefix.cc
new file mode 100644 (file)
index 0000000..dd7b193
--- /dev/null
@@ -0,0 +1,210 @@
+#include "database-private.h"
+#include "query-fp.h"
+#include "thread-fp.h"
+#include "regexp-fields.h"
+#include "parse-time-vrp.h"
+
+typedef struct {
+    const char *name;
+    const char *prefix;
+    notmuch_field_flag_t flags;
+} prefix_t;
+
+/* With these prefix values we follow the conventions published here:
+ *
+ * https://xapian.org/docs/omega/termprefixes.html
+ *
+ * as much as makes sense. Note that I took some liberty in matching
+ * the reserved prefix values to notmuch concepts, (for example, 'G'
+ * is documented as "newsGroup (or similar entity - e.g. a web forum
+ * name)", for which I think the thread is the closest analogue in
+ * notmuch. This in spite of the fact that we will eventually be
+ * storing mailing-list messages where 'G' for "mailing list name"
+ * might be even a closer analogue. I'm treating the single-character
+ * prefixes preferentially for core notmuch concepts (which will be
+ * nearly universal to all mail messages).
+ */
+
+static const
+prefix_t prefix_table[] = {
+    /* name                    term prefix     flags */
+    { "type",                   "T",            NOTMUCH_FIELD_NO_FLAGS },
+    { "reference",              "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
+    { "replyto",                "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
+    { "directory",              "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "file-direntry",          "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "directory-direntry",     "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "body",                   "",             NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "thread",                 "G",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "tag",                    "K",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "is",                     "K",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "id",                     "Q",            NOTMUCH_FIELD_EXTERNAL },
+    { "mid",                    "Q",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "path",                   "P",            NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "property",               "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
+    /*
+     * Unconditionally add ':' to reduce potential ambiguity with
+     * overlapping prefixes and/or terms that start with capital
+     * letters. See Xapian document termprefixes.html for related
+     * discussion.
+     */
+    { "folder",                 "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "from",                   "XFROM",        NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "to",                     "XTO",          NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "attachment",             "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "mimetype",               "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "subject",                "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC |
+      NOTMUCH_FIELD_PROCESSOR },
+};
+
+static const char *
+_user_prefix (void *ctx, const char *name)
+{
+    return talloc_asprintf (ctx, "XU%s:", name);
+}
+
+const char *
+_find_prefix (const char *name)
+{
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+       if (strcmp (name, prefix_table[i].name) == 0)
+           return prefix_table[i].prefix;
+    }
+
+    INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
+
+    return "";
+}
+
+/* Like find prefix, but include the possibility of user defined
+ * prefixes specific to this database */
+
+const char *
+_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
+{
+    unsigned int i;
+
+    /*XXX TODO: reduce code duplication */
+    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+       if (strcmp (name, prefix_table[i].name) == 0)
+           return prefix_table[i].prefix;
+    }
+
+    if (notmuch->user_prefix)
+       return _notmuch_string_map_get (notmuch->user_prefix, name);
+
+    return NULL;
+}
+
+static void
+_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    if (prefix->prefix)
+       notmuch->query_parser->add_prefix ("", prefix->prefix);
+    if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
+       notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+    else
+       notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
+}
+
+static void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
+       Xapian::FieldProcessor *fp;
+
+       if (STRNCMP_LITERAL (prefix->name, "date") == 0)
+           fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
+       else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
+           fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+       else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
+           fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+       else
+           fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
+                                           *notmuch->query_parser, notmuch))->release ();
+
+       /* we treat all field-processor fields as boolean in order to get the raw input */
+       if (prefix->prefix)
+           notmuch->query_parser->add_prefix ("", prefix->prefix);
+       notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
+    } else {
+       _setup_query_field_default (prefix, notmuch);
+    }
+}
+
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch)
+{
+    for (unsigned int i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+       const prefix_t *prefix = &prefix_table[i];
+       if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
+           _setup_query_field (prefix, notmuch);
+       }
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch)
+{
+    notmuch_config_list_t *list;
+    notmuch_status_t status;
+
+    notmuch->user_prefix = _notmuch_string_map_create (notmuch);
+    if (notmuch->user_prefix == NULL)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    notmuch->user_header = _notmuch_string_map_create (notmuch);
+    if (notmuch->user_header == NULL)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
+    if (status)
+       return status;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+
+       prefix_t query_field;
+
+       const char *key = notmuch_config_list_key (list)
+                         + sizeof (CONFIG_HEADER_PREFIX) - 1;
+
+       _notmuch_string_map_append (notmuch->user_prefix,
+                                   key,
+                                   _user_prefix (notmuch, key));
+
+       _notmuch_string_map_append (notmuch->user_header,
+                                   key,
+                                   notmuch_config_list_value (list));
+
+       query_field.name = talloc_strdup (notmuch, key);
+       query_field.prefix = _user_prefix (notmuch, key);
+       query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
+                           | NOTMUCH_FIELD_EXTERNAL;
+
+       _setup_query_field_default (&query_field, notmuch);
+    }
+
+    notmuch_config_list_destroy (list);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
index 0cf69975f44275c8f43f6939f363fd0111cc649f..cef07095caf329910c7c997ece5f50b640b06177 100755 (executable)
@@ -39,6 +39,16 @@ deleted_id=$gen_msg_id
 output=$(notmuch search subject:deleted | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
 
+test_begin_subtest "Search, exclude \"deleted\" messages; alternate config file"
+cp ${NOTMUCH_CONFIG} alt-config
+notmuch config set search.exclude_tags
+notmuch --config=alt-config search subject:deleted | notmuch_search_sanitize > OUTPUT
+cp alt-config ${NOTMUCH_CONFIG}
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "Search, exclude \"deleted\" messages from message search"
 output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize)
 test_expect_equal "$output" "id:$not_deleted_id"
index 360e45b08913eae399694c5d224a85aeec628ed1..8c34acf91d8ebd89e3b3c711ab1dbeb61559dcfc 100755 (executable)
@@ -28,18 +28,18 @@ EOF
 test_begin_subtest "notmuch_database_{set,get}_config"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
-   EXPECT0(notmuch_database_set_config (db, "testkey1", "testvalue1"));
-   EXPECT0(notmuch_database_set_config (db, "testkey2", "testvalue2"));
-   EXPECT0(notmuch_database_get_config (db, "testkey1", &val));
-   printf("testkey1 = %s\n", val);
-   EXPECT0(notmuch_database_get_config (db, "testkey2", &val));
-   printf("testkey2 = %s\n", val);
+   EXPECT0(notmuch_database_set_config (db, "test.key1", "testvalue1"));
+   EXPECT0(notmuch_database_set_config (db, "test.key2", "testvalue2"));
+   EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+   printf("test.key1 = %s\n", val);
+   EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+   printf("test.key2 = %s\n", val);
 }
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-testkey1 = testvalue1
-testkey2 = testvalue2
+test.key1 = testvalue1
+test.key2 = testvalue2
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -93,8 +93,8 @@ EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 aaabefore beforeval
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
 zzzafter afterval
 == stderr ==
 EOF
@@ -115,8 +115,8 @@ EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 aaabefore 1
-testkey1 1
-testkey2 1
+test.key1 1
+test.key2 1
 zzzafter 1
 == stderr ==
 EOF
@@ -126,7 +126,7 @@ test_begin_subtest "notmuch_database_get_config_list: one prefix"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
    notmuch_config_list_t *list;
-   EXPECT0(notmuch_database_get_config_list (db, "testkey", &list));
+   EXPECT0(notmuch_database_get_config_list (db, "test.key", &list));
    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
       printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
    }
@@ -135,8 +135,8 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -152,8 +152,8 @@ cat <<'EOF' >EXPECTED
 #notmuch-dump batch-tag:3 config
 #@ aaabefore beforeval
 #@ key%20with%20spaces value,%20with,%20spaces%21
-#@ testkey1 testvalue1
-#@ testkey2 testvalue2
+#@ test.key1 testvalue1
+#@ test.key2 testvalue2
 #@ zzzafter afterval
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -162,7 +162,7 @@ test_begin_subtest "restore config"
 notmuch dump --include=config >EXPECTED
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
-    EXPECT0(notmuch_database_set_config (db, "testkey1", "mutatedvalue"));
+    EXPECT0(notmuch_database_set_config (db, "test.key1", "mutatedvalue"));
 }
 EOF
 notmuch restore --include=config <EXPECTED