branch: externals/denote
commit 5c7208e4edddeca4c2d9ef9e585c7a023a906109
Author: Jean-Philippe Gagné Guay <jeanphilippe...@gmail.com>
Commit: Jean-Philippe Gagné Guay <jeanphilippe...@gmail.com>

    Make denote-directory accept a list of directories
---
 denote.el | 156 ++++++++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 107 insertions(+), 49 deletions(-)

diff --git a/denote.el b/denote.el
index edd30d07dc..4e2c40d384 100644
--- a/denote.el
+++ b/denote.el
@@ -123,14 +123,16 @@
 
 ;; About the autoload: (info "(elisp) File Local Variables")
 
-;;;###autoload (put 'denote-directory 'safe-local-variable (lambda (val) (or 
(stringp val) (eq val 'local) (eq val 'default-directory))))
+;;;###autoload (put 'denote-directory 'safe-local-variable (lambda (val) (or 
(stringp val) (listp val) (eq val 'local) (eq val 'default-directory))))
 (defcustom denote-directory (expand-file-name "~/Documents/notes/")
   "Directory for storing personal notes.
 
+This can also be a list of directories.
+
 If you intend to reference this variable in Lisp, consider using
-the function `denote-directory' instead."
+the function `denote-directories' instead."
   :group 'denote
-  :safe (lambda (val) (or (stringp val) (eq val 'local) (eq val 
'default-directory)))
+  :safe (lambda (val) (or (stringp val) (listp val) (eq val 'local) (eq val 
'default-directory)))
   :package-version '(denote . "2.0.0")
   :link '(info-link "(denote) Maintain separate directories for notes")
   :type 'directory)
@@ -958,12 +960,7 @@ Like `denote--completion-table' but also disable sorting."
      ((stringp dir-locals)
       dir-locals))))
 
-(defun denote--make-denote-directory ()
-  "Make the variable `denote-directory' and its parents, if needed."
-  (when (not (file-directory-p denote-directory))
-    (make-directory denote-directory :parents)))
-
-(defun denote-directory ()
+(defun denote-directories ()
   "Return path of variable `denote-directory' as a proper directory.
 Custom Lisp code can `let' bind the variable `denote-directory'
 to override what this function returns."
@@ -972,12 +969,34 @@ to override what this function returns."
       (progn
         (display-warning
          'denote
-         "Silo value must be a string; `local' or `default-directory'are 
obsolete"
+         "Silo value must be a string; `local' or `default-directory' are 
obsolete"
          :error)
         silo-dir)
-    (let ((denote-directory (file-name-as-directory (expand-file-name 
denote-directory))))
-      (denote--make-denote-directory)
-      denote-directory)))
+    (let ((denote-directories
+           (if (listp denote-directory)
+               (mapcar (lambda (d)
+                         (file-name-as-directory (expand-file-name d)))
+                       denote-directory)
+             (list (file-name-as-directory (expand-file-name 
denote-directory))))))
+      (mapc (lambda (d)
+              (when (not (file-directory-p d))
+                (make-directory d :parents)))
+            denote-directories)
+      denote-directories)))
+
+(defun denote-has-single-denote-directory-p ()
+  "Return non-nil if `denote-directory' is a single item."
+  (not (cdr (denote-directories))))
+
+(defun denote-directory ()
+  "Return path of variable `denote-directory' as a proper directory.
+
+This function is obsolete. Use `denote-directories' instead, which
+returns the note directories as a list.  The current function only
+returns the first directory."
+  (car (denote-directories)))
+
+(make-obsolete 'denote-directory 'denote-directories "4.1.0")
 
 ;; TODO: Review and fix the features listed in the docstring below before
 ;; making this a user option.
@@ -1137,7 +1156,10 @@ extensions are those implied by the variable 
`denote-file-type'."
 
 (defun denote-file-is-in-denote-directory-p (file)
   "Return non-nil if FILE is in the variable `denote-directory'."
-  (string-prefix-p (denote-directory) (expand-file-name file)))
+  (seq-some
+   (lambda (d)
+     (string-prefix-p d (expand-file-name file)))
+   (denote-directories)))
 
 (defun denote-filename-is-note-p (filename)
   "Return non-nil if FILENAME is a valid name for a Denote note.
@@ -1195,11 +1217,14 @@ what remains."
 (defun denote-get-file-name-relative-to-denote-directory (file)
   "Return name of FILE relative to the variable `denote-directory'.
 FILE must be an absolute path."
-  (when-let* ((dir (denote-directory))
+  (when-let* ((directories (denote-directories))
               ((file-name-absolute-p file))
               (file-name (expand-file-name file))
-              ((string-prefix-p dir file-name)))
-    (substring-no-properties file-name (length dir))))
+              (directory (seq-find
+                          (lambda (d)
+                            (string-prefix-p d file-name))
+                          directories)))
+    (substring-no-properties file-name (length directory))))
 
 (defun denote-extract-id-from-string (string)
   "Return existing Denote identifier in STRING, else nil."
@@ -1226,12 +1251,16 @@ Return t if FILE is valid, else return nil."
   "Return list of all files in variable `denote-directory'.
 Avoids traversing dotfiles (unconditionally) and whatever matches
 `denote-excluded-directories-regexp'."
-  (directory-files-recursively
-   (denote-directory)
-   directory-files-no-dot-files-regexp
-   :include-directories
-   #'denote--directory-files-recursively-predicate
-   :follow-symlinks))
+  (apply #'append
+         (mapcar
+          (lambda (directory)
+            (directory-files-recursively
+             directory
+             directory-files-no-dot-files-regexp
+             :include-directories
+             #'denote--directory-files-recursively-predicate
+             :follow-symlinks))
+          (denote-directories))))
 
 (defun denote--file-excluded-p (file)
   "Return non-file if FILE matches `denote-excluded-files-regexp'."
@@ -1394,21 +1423,31 @@ select a file.
 With optional NO-REQUIRE-MATCH, accept the given input as-is.
 
 Return the absolute path to the matching file."
-  (let* ((default-directory (denote-directory))
+  (let* (;; Some external program may use `default-directory' with the
+         ;; relative file paths of the completion candidates.
+         (default-directory (car (denote-directories)))
+         (files (denote-directory-files
+                 (or denote-file-prompt-use-files-matching-regexp 
files-matching-regexp)
+                 :omit-current))
          (relative-files (mapcar
                           #'denote-get-file-name-relative-to-denote-directory
-                          (denote-directory-files
-                           (or denote-file-prompt-use-files-matching-regexp 
files-matching-regexp)
-                           :omit-current)))
-         (prompt (format "%s in %s: "
-                         (or prompt-text "Select FILE")
-                         (propertize (denote-directory) 'face 
'denote-faces-prompt-current-name)))
+                          files))
+         (prompt (if (denote-has-single-denote-directory-p)
+                     (format "%s in %s:"
+                             (or prompt-text "Select FILE")
+                             (propertize (car (denote-directories)) 'face 
'denote-faces-prompt-current-name))
+                   (format "%s: " (or prompt-text "Select FILE"))))
          (input (completing-read
                  prompt
-                 (denote--completion-table 'file relative-files)
+                 (denote--completion-table 'file
+                                           (if 
(denote-has-single-denote-directory-p)
+                                               relative-files
+                                             files))
                  nil (unless no-require-match :require-match)
                  nil 'denote-file-history))
-         (absolute-file (expand-file-name input (denote-directory))))
+         (absolute-file (if (denote-has-single-denote-directory-p)
+                            (expand-file-name input (car (denote-directories)))
+                          input)))
     ;; NOTE: This block is executed when no-require-match is t. It is useful
     ;; for commands such as `denote-open-or-create` or similar.
     (unless (file-exists-p absolute-file)
@@ -1743,6 +1782,8 @@ When called from Lisp, the arguments are a string, a 
symbol among
 `denote-sort-components', a non-nil value, and a string, respectively."
   (interactive
    (append (list (denote-files-matching-regexp-prompt)) 
(denote-sort-dired--prompts)))
+  (unless (denote-has-single-denote-directory-p)
+    (user-error "This command does not work when `denote-directory' has 
multiple directories"))
   (let ((component (or sort-by-component
                        denote-sort-dired-default-sort-component
                        'identifier))
@@ -1750,9 +1791,10 @@ When called from Lisp, the arguments are a string, a 
symbol among
                           denote-sort-dired-default-reverse-sort
                           nil))
         (exclude-rx (or exclude-regexp nil)))
-    (if-let* ((default-directory (denote-directory))
+    (if-let* (;; See comment in `denote-file-prompt'.
+              (default-directory (car (denote-directories)))
               (files (denote-sort-get-directory-files files-matching-regexp 
component reverse-sort nil exclude-rx)))
-        (let ((dired-buffer (dired (cons (denote-directory) (mapcar 
#'file-relative-name files))))
+        (let ((dired-buffer (dired (cons (car (denote-directories)) (mapcar 
#'file-relative-name files))))
               (buffer-name (funcall denote-sort-dired-buffer-name-function 
files-matching-regexp component reverse-sort exclude-rx)))
           (setq denote-sort--dired-buffer dired-buffer)
           (with-current-buffer dired-buffer
@@ -2556,7 +2598,7 @@ DIR-PATH, ID, KEYWORDS, TITLE, EXTENSION and SIGNATURE are
 expected to be supplied by `denote' or equivalent command.
 
 DIR-PATH is a string pointing to a directory.  It ends with a
-forward slash (the function `denote-directory' makes sure this is
+forward slash (the function `denote-directories' makes sure this is
 the case when returning the value of the variable `denote-directory').
 DIR-PATH cannot be nil or an empty string.
 
@@ -2660,7 +2702,10 @@ TEMPLATE, and SIGNATURE should be valid for note 
creation."
 
 (defun denote--dir-in-denote-directory-p (directory)
   "Return non-nil if DIRECTORY is in variable `denote-directory'."
-  (string-prefix-p (denote-directory) (expand-file-name directory)))
+  (seq-some
+   (lambda (d)
+     (string-prefix-p d (expand-file-name directory)))
+   (denote-directories)))
 
 (defun denote--valid-file-type (filetype)
   "Return a valid filetype symbol given the argument FILETYPE.
@@ -3005,7 +3050,7 @@ instead of that of the parameter."
          (date (if (string-empty-p id) nil (date-to-time id)))
          (directory (if (and directory (denote--dir-in-denote-directory-p 
directory))
                         (file-name-as-directory directory)
-                      (denote-directory)))
+                      (car (denote-directories))))
          (template (if (or (stringp template) (functionp template))
                        template
                      (or (alist-get template denote-templates) "")))
@@ -3036,8 +3081,8 @@ When called from Lisp, all arguments are optional.
 - DIRECTORY is a string representing the path to either the
   value of the variable `denote-directory' or a subdirectory
   thereof.  The subdirectory must exist: Denote will not create
-  it.  If DIRECTORY does not resolve to a valid path, the
-  variable `denote-directory' is used instead.
+  it.  If DIRECTORY does not resolve to a valid path, the first
+  item in the variable `denote-directory' is used instead.
 
 - DATE is a string representing a date like 2022-06-30 or a date
   and time like 2022-06-16 14:30.  A nil value or an empty string
@@ -3194,9 +3239,12 @@ Optional INITIAL-DATE and PROMPT-TEXT have the same 
meaning as
   "Prompt for subdirectory of the variable `denote-directory'.
 The table uses the `file' completion category (so it works with
 packages such as `marginalia' and `embark')."
-  (let* ((root (directory-file-name (denote-directory)))
+  (let* ((roots (mapcar
+                 (lambda (d)
+                   (directory-file-name d))
+                 (denote-directories)))
          (subdirs (denote-directory-subdirectories))
-         (dirs (push root subdirs)))
+         (dirs (append roots subdirs)))
     (denote--subdirs-completion-table dirs)))
 
 (defvar denote-template-history nil
@@ -4652,7 +4700,9 @@ and seconds."
   "Integration between Denote and Dired."
   :group 'denote)
 
-(defcustom denote-dired-directories (list denote-directory)
+(defcustom denote-dired-directories (if (listp denote-directory)
+                                        denote-directory
+                                      (list denote-directory))
   "List of directories where `denote-dired-mode' should apply to.
 For this to take effect, add `denote-dired-mode-in-directories',
 to the `dired-mode-hook'.
@@ -4964,12 +5014,16 @@ selected one.
 
 With optional PROMPT-TEXT use it for the minibuffer prompt instead of
 the generic one."
-  (let* ((file-names (mapcar 
#'denote-get-file-name-relative-to-denote-directory files))
+  (let* ((file-names (if (denote-has-single-denote-directory-p)
+                         (mapcar 
#'denote-get-file-name-relative-to-denote-directory files)
+                       files))
          (selected (completing-read
                     (format-prompt (or prompt-text "Select file among files") 
nil)
                     (denote--completion-table 'file file-names)
                     nil t nil 'denote-link-find-file-history)))
-    (expand-file-name selected (denote-directory))))
+    (if (denote-has-single-denote-directory-p)
+        (expand-file-name selected (car (denote-directories)))
+      selected)))
 
 (define-obsolete-function-alias
   'denote-link-return-links
@@ -5289,7 +5343,7 @@ concomitant alist, such as 
`denote-backlinks-display-buffer-action'."
          ;; in relative form, but eventually notes may not be all
          ;; under a common directory (or project).
          (xref-alist (denote-retrieve-xref-alist query files))
-         (dir (denote-directory)))
+         (dirs (denote-directories)))
     (unless xref-alist
       (error "No matches for query `%s'" query))
     ;; Update internal variables
@@ -5319,7 +5373,7 @@ concomitant alist, such as 
`denote-backlinks-display-buffer-action'."
       ;; `denote-directory' here because the buttons depend on it.
       ;; Moreover, its value is overwritten after enabling the major
       ;; mode, so it needs to be set after.
-      (setq-local denote-directory dir)
+      (setq-local denote-directory dirs)
       (setq overlay-arrow-position nil)
       (goto-char (point-min))
       (xref--insert-xrefs xref-alist)
@@ -6036,12 +6090,16 @@ contents, not file names.  Optional ID-ONLY has the 
same meaning as in
 ;; NOTE 2022-07-21: I don't think we need a history for this one.
 (defun denote-link--buffer-file-prompt (buffer-file-names)
   "Select file from BUFFER-FILE-NAMES of Denote notes."
-  (let* ((relative-buffer-file-names (mapcar 
#'denote-get-file-name-relative-to-denote-directory buffer-file-names))
+  (let* ((file-names (if (denote-has-single-denote-directory-p)
+                         (mapcar 
#'denote-get-file-name-relative-to-denote-directory buffer-file-names)
+                       buffer-file-names))
          (selected (completing-read
                     "Select open note to add links to: "
-                    (denote--completion-table 'file relative-buffer-file-names)
+                    (denote--completion-table 'file file-names)
                     nil t)))
-    (expand-file-name selected (denote-directory))))
+    (if (denote-has-single-denote-directory-p)
+        (expand-file-name selected (car (denote-directories)))
+      selected)))
 
 (defun denote-link--map-over-notes ()
   "Return list of `denote-file-has-denoted-filename-p' from Dired marked 
items."

Reply via email to