branch: externals/denote-search
commit 988ac9d7a8525b520832d98c764a18b6d53d8122
Author: Lucas Quintana <lm...@protonmail.com>
Commit: Lucas Quintana <lm...@protonmail.com>

    Allow filtering by multiple keywords at once
---
 README.org       | 14 +++++-----
 denote-search.el | 80 ++++++++++++++++++++++++++++++++++++--------------------
 2 files changed, 59 insertions(+), 35 deletions(-)

diff --git a/README.org b/README.org
index f066572f9d..32ed713f4b 100644
--- a/README.org
+++ b/README.org
@@ -261,17 +261,19 @@ It's possible that you don't want to start a new search, 
but rather to
 search something on the curated file list you got.  See [[*focused 
search][focused
 search]].
 
-#+findex: denote-search-exclude-files-with-keyword
-#+findex: denote-search-only-include-files-with-keyword
-Filtering by keyword is such a common operation that two special
+#+findex: denote-search-exclude-files-with-keywords
+#+findex: denote-search-only-include-files-with-keywords
+Filtering by keywords is such a common operation that two special
 commands exist just for that: ~X~
-(~denote-search-exclude-files-with-keyword~) and ~I~
-(~denote-search-only-include-files-with-keyword~).  They are
+(~denote-search-exclude-files-with-keywords~) and ~I~
+(~denote-search-only-include-files-with-keywords~).  They are
 equivalent to calling its regular counterparts and issuing a word with
 a leading underscore; however, they also offer completion for
 available keywords (using ~denote-keywords~, so its actual behaviour
 is governed by the variables ~denote-infer-keywords~ and
-~denote-known-keywords~).
+~denote-known-keywords~).  But the main advantage is that they allow
+issuing multiple keywords at once, separated by commas (or whatever
+the value of ~crm-separator~ is, which should be a comma).
 
 History is available when filtering.  Press ~M-p~
 (~previous-history-element~) to view past queries.  This history is
diff --git a/denote-search.el b/denote-search.el
index 30126d97ea..a85a2d95c4 100644
--- a/denote-search.el
+++ b/denote-search.el
@@ -136,16 +136,20 @@ non-nil."
            "Only include file names matching: ")
          nil 'denote-search-file-regexp-history)))
 
-(defun denote-search-keyword-prompt (&optional include)
-  "Prompt for a keyword in the minibuffer, with completion.
+(defun denote-search-keywords-prompt (&optional include)
+  "Prompt for keywords in the minibuffer, with completion.
 
-The prompt assumes the user wants to exclude the keyword, unless INCLUDE
-is non-nil."
-  (list (completing-read
-         (if (not include)
-             "Exclude files with keyword: "
-           "Only include files with keyword: ")
-         (denote-keywords) nil t nil 'denote-keyword-history)))
+Keywords are read using `completing-read-multiple'.
+
+The prompt assumes the user wants to exclude the keywords, unless
+INCLUDE is non-nil."
+  (list
+   (delete-dups
+    (completing-read-multiple
+     (if (not include)
+         "Exclude files with keywords: "
+       "Only include files with keywords: ")
+     (denote-keywords) nil t nil 'denote-keyword-history))))
 
 (defun denote-search-query-prompt (&optional type)
   "Prompt for a search query in the minibuffer.
@@ -355,11 +359,22 @@ RET’.
 
 Internally, this works by generating a new call to `denote-search' with
 the same QUERY as the last one, but with a restricted SET gotten from
-checking REGEXP against last matched files."
+checking REGEXP against last matched files.
+
+When called from Lisp, REGEXP can be a list; in that case, it should be
+a list of fixed strings (NOT regexps) to check against last matched
+files.  Files that match any of the strings get excluded.  Internally,
+the list is processed using `regexp-opt', which see.  For an example of
+this usage, see `denote-search-exclude-files-with-keywords'."
   (interactive (denote-search-file-regexp-prompt))
   (let (final-files)
     (dolist (file denote-search--last-files)
-      (unless (string-match regexp file)
+      (unless (string-match
+               ;; Support list of strings as REGEXP
+               (if (listp regexp)
+                   (regexp-opt regexp)
+                 regexp)
+               file)
         (push file final-files)))
     (if final-files
         (denote-search denote-search--last-query final-files)
@@ -368,33 +383,40 @@ checking REGEXP against last matched files."
 (defun denote-search-only-include-files (regexp)
   "Exclude file names not matching REGEXP from current `denote-search' buffer.
 
-See also `denote-search-exlude-files'."
+See `denote-search-exlude-files' for details, including the behaviour
+when REGEXP is a list."
   (interactive (denote-search-file-regexp-prompt :include))
   (let (final-files)
     (dolist (file denote-search--last-files)
-      (when (string-match regexp file)
+      (when (string-match
+             ;; Support list of strings as REGEXP
+             (if (listp regexp)
+                 (regexp-opt regexp)
+               regexp)
+             file)
         (push file final-files)))
     (if final-files
         (denote-search denote-search--last-query final-files)
       (user-error "No remaining files when applying that filter"))))
 
-(defun denote-search-exclude-files-with-keyword (keyword)
-  "Exclude files with KEYWORD from current `denote-search' buffer.
+(defun denote-search-exclude-files-with-keywords (keywords)
+  "Exclude files with KEYWORDS from current `denote-search' buffer.
+
+KEYWORDS should be a list of keywords (without underscore).
 
-This is equivalent to passing the argument \"_KEYWORD\" to
-`denote-search-exclude-files'.  This command, however, offers completion
-for available keywords."
-  (interactive (denote-search-keyword-prompt))
-  (denote-search-exclude-files (concat "_" keyword)))
+Interactively, KEYWORDS are read from the minibuffer using
+`completing-read-multiple', which see."
+  (interactive (denote-search-keywords-prompt))
+  (denote-search-exclude-files
+   (mapcar (lambda (kw) (concat "_" kw)) keywords)))
 
-(defun denote-search-only-include-files-with-keyword (keyword)
-  "Exclude files without KEYWORD from current `denote-search' buffer.
+(defun denote-search-only-include-files-with-keywords (keywords)
+  "Exclude files without KEYWORDS from current `denote-search' buffer.
 
-This is equivalent to passing the argument \"_KEYWORD\" to
-`denote-search-only-include-files'.  This command, however, offers
-completion for available keywords."
-  (interactive (denote-search-keyword-prompt :include))
-  (denote-search-only-include-files (concat "_" keyword)))
+See `denote-search-exclude-files-with-keywords' for details."
+  (interactive (denote-search-keywords-prompt :include))
+  (denote-search-only-include-files
+   (mapcar (lambda (kw) (concat "_" kw)) keywords)))
 
 ;;;; Keymap and mode definition:
 
@@ -409,8 +431,8 @@ completion for available keywords."
   "v" #'outline-cycle
   "x" #'denote-search-exclude-files
   "i" #'denote-search-only-include-files
-  "X" #'denote-search-exclude-files-with-keyword
-  "I" #'denote-search-only-include-files-with-keyword)
+  "X" #'denote-search-exclude-files-with-keywords
+  "I" #'denote-search-only-include-files-with-keywords)
 
 (define-minor-mode denote-search-mode
   "Minor mode enabled in the buffer generated by `denote-search'.

Reply via email to