branch: externals/denote
commit 035e1452febdc5b5add7d0645f90b265284298cc
Author: Protesilaos Stavrou <i...@protesilaos.com>
Commit: Protesilaos Stavrou <i...@protesilaos.com>

    Merge denote-sort.el into denote.el
    
    This is part of the wider reorganisation of the project, as discussed
    in issue 543: <https://github.com/protesilaos/denote/issues/543>.
---
 README.org     |  24 +++--
 denote-sort.el | 335 ---------------------------------------------------------
 denote.el      | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+), 344 deletions(-)

diff --git a/README.org b/README.org
index 88d7e9f4ee..e8b07ef16c 100644
--- a/README.org
+++ b/README.org
@@ -4262,16 +4262,20 @@ is also available 
([[#h:8ec774f4-4154-4599-b8cf-fa767e18d22d][Find a relative of
 :CUSTOM_ID: h:9fe01e63-f34f-4479-8713-f162a5ca865e
 :END:
 
-The =denote-sort.el= file is an optional extension to the core
-functionality of Denote, which empowers users to sort files by the
-given file name component ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The 
file-naming scheme]]).
+[ As part of {{{development-version}}}, the code of =denote-sort.el=
+  is merged into =denote.el= as part of a wider reorganisation of the
+  project. ]
+
+The =denote.el= file contains functions which empowers user or
+developers to sort files by the given file name component 
([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]).
 
 #+findex: denote-sort-dired
-The command ~denote-sort-dired~ produces a Dired file listing with a
-flat, filtered, and sorted set of files from the ~denote-directory~
-([[#h:c958e087-1d23-4a25-afdd-db7bf5606b4c][Define a sorting function per 
component]]). It does so by a series of
-prompts, which can be configured by the user option
-~denote-sort-dired-extra-prompts~ 
([[#h:a34228cb-484f-48fe-9cbc-8e41f313127b][Configure what extra prompts 
~denote-sort-dired~ issues]]).
+#+findex: denote-dired
+The command ~denote-sort-dired~ (alias ~denote-dired~) produces a
+Dired file listing with a flat, filtered, and sorted set of files from
+the ~denote-directory~ ([[#h:c958e087-1d23-4a25-afdd-db7bf5606b4c][Define a 
sorting function per component]]). It
+does so by a series of prompts, which can be configured by the user
+option ~denote-sort-dired-extra-prompts~ 
([[#h:a34228cb-484f-48fe-9cbc-8e41f313127b][Configure what extra prompts 
~denote-sort-dired~ issues]]).
 
 The out-of-the-box behaviour of ~denote-sort-dired~ is as follows:
 
@@ -4289,8 +4293,10 @@ The out-of-the-box behaviour of ~denote-sort-dired~ is 
as follows:
 The resulting Dired listing is a regular Dired buffer, unlike that of
 ~dired-virtual-mode~ ([[#h:d35d8d41-f51b-4139-af8f-9c8cc508e35b][Use 
~dired-virtual-mode~ for arbitrary file listings]]).
 
+#+findex: denote-sort-files
 The dynamic Org blocks that Denote defines to insert file contents
-also use this feature ([[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic 
block to insert file contents]]).
+also use this feature internally by means of the non-interactive
+function ~denote-sort-files~ ([[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org 
dynamic block to insert file contents]]).
 
 ** Configure what extra prompts ~denote-sort-dired~ issues
 :PROPERTIES:
diff --git a/denote-sort.el b/denote-sort.el
deleted file mode 100644
index 0ee5be5bd8..0000000000
--- a/denote-sort.el
+++ /dev/null
@@ -1,335 +0,0 @@
-;;; denote-sort.el --- Sort Denote files based on a file name component -*- 
lexical-binding: t -*-
-
-;; Copyright (C) 2023-2025  Free Software Foundation, Inc.
-
-;; Author: Protesilaos Stavrou <i...@protesilaos.com>
-;; Maintainer: Protesilaos Stavrou <i...@protesilaos.com>
-;; URL: https://github.com/protesilaos/denote
-
-;; This file is NOT part of GNU Emacs.
-
-;; This program is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-;;
-;; Sort Denote files based on their file name components, namely, the
-;; signature, title, or keywords.
-
-;;; Code:
-
-(require 'denote)
-
-(defgroup denote-sort nil
-  "Sort Denote files based on a file name component."
-  :group 'denote
-  :link '(info-link "(denote) Top")
-  :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote";))
-
-(defconst denote-sort-comparison-fallback-function #'string-collate-lessp
-  "String comparison function used by `denote-sort-files' subroutines.")
-
-(defconst denote-sort-components '(title keywords signature identifier)
-  "List of sorting keys applicable for `denote-sort-files' and related.")
-
-(defcustom denote-sort-identifier-comparison-function 
denote-sort-comparison-fallback-function
-  "Function to sort the DATE/IDENTIFIER component in file names.
-The function accepts two arguments and must return a non-nil value if
-the first argument is smaller than the second one."
-  :type 'function
-  :package-version '(denote . "3.2.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-title-comparison-function 
denote-sort-comparison-fallback-function
-  "Function to sort the TITLE component in file names.
-The function accepts two arguments and must return a non-nil value if
-the first argument is smaller than the second one."
-  :type 'function
-  :package-version '(denote . "3.1.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-keywords-comparison-function 
denote-sort-comparison-fallback-function
-  "Function to sort the KEYWORDS component in file names.
-The function accepts two arguments and must return a non-nil value if
-the first argument is smaller than the second one."
-  :type 'function
-  :package-version '(denote . "3.1.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-signature-comparison-function 
denote-sort-comparison-fallback-function
-  "Function to sort the SIGNATURE component in file names.
-The function accepts two arguments and must return a non-nil value if
-the first argument is smaller than the second one."
-  :type 'function
-  :package-version '(denote . "3.1.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-dired-extra-prompts '(sort-by-component reverse-sort)
-  "Determine what `denote-sort-dired' prompts for beside a search query.
-This concerns the additional prompts issued by `denote-sort-dired' about
-whether to sort by a given file name component and to then reverse the
-sort.
-
-The value is a list of symbols, which can include the symbols
-`sort-by-component', `reverse-sort', and `exclude-regexp'.  The order is
-significant, with the leftmost symbol coming first.
-
-These symbols correspond to the following:
-
-- A choice to select the file name component to sort by.
-- A yes or no prompt on whether to reverse the sorting.
-- A string (or regular expression) of files to be excluded from the results.
-
-If the value is nil, skip all prompts.  In this scenario, the sorting is
-done according to `denote-sort-dired-default-sort-component' and
-`denote-sort-dired-default-reverse-sort'."
-  :type '(radio (const :tag "Do not prompt for anything" nil)
-                (set :tag "Available prompts" :greedy t
-                     (const :tag "Sort by file name component" 
sort-by-component)
-                     (const :tag "Reverse the sort" reverse-sort)
-                     (const :tag "Exclude files matching regexp" 
exclude-regexp)))
-  :package-version '(denote . "3.2.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-dired-default-sort-component 'identifier
-  "Set the default file name component to sort by.
-This is used only if `denote-sort-dired-extra-prompts' omits the
-minibuffer prompt for which file name component to sort by."
-  :type '(radio
-          (const :tag "Sort by identifier (default)" identifier)
-          (const :tag "Sort by title" title)
-          (const :tag "Sort by keywords" keywords)
-          (const :tag "Sort by signature" signature))
-  :package-version '(denote . "3.1.0")
-  :group 'denote-sort)
-
-(defcustom denote-sort-dired-default-reverse-sort nil
-  "If non-nil, reverse the sorting order by default.
-This is used only if `denote-sort-dired-extra-prompts' omits the
-minibuffer prompt that asks for a reverse sort or not."
-  :type 'boolean
-  :package-version '(denote . "3.1.0")
-  :group 'denote-sort)
-
-;; NOTE 2023-12-04: We can have compound sorting algorithms such as
-;; title+signature, but I want to keep this simple for the time being.
-;; Let us first hear from users to understand if there is a real need
-;; for such a feature.
-(defmacro denote-sort--define-lessp (component)
-  "Define function to sort by COMPONENT."
-  (let ((retrieve-fn (intern (format "denote-retrieve-filename-%s" component)))
-        (comparison-fn (intern (format "denote-sort-%s-comparison-function" 
component))))
-    `(defun ,(intern (format "denote-sort-%s-lessp" component)) (file1 file2)
-       ,(format
-         "Return smallest among FILE1, FILE2 based on their %s.
-The `%s' performs the comparison."
-         component comparison-fn)
-       (let* ((one (,retrieve-fn file1))
-              (two (,retrieve-fn file2))
-              (one-empty-p (or (null one) (string-empty-p one)))
-              (two-empty-p (or (null two) (string-empty-p two))))
-         (cond
-          (one-empty-p nil)
-          ((and (not one-empty-p) two-empty-p) one)
-          (t (funcall (or ,comparison-fn 
denote-sort-comparison-fallback-function) one two)))))))
-
-;; TODO 2023-12-04: Subject to the above NOTE, we can also sort by
-;; directory and by file length.
-(denote-sort--define-lessp identifier)
-(denote-sort--define-lessp title)
-(denote-sort--define-lessp keywords)
-(denote-sort--define-lessp signature)
-
-;;;###autoload
-(defun denote-sort-files (files component &optional reverse)
-  "Returned sorted list of Denote FILES.
-
-With COMPONENT as a symbol among `denote-sort-components',
-sort files based on the corresponding file name component.
-
-With COMPONENT as the symbol of a function, use it to perform the
-sorting.  In this case, the function is called with two arguments, as
-described by `sort'.
-
-With COMPONENT as a nil value keep the original date-based
-sorting which relies on the identifier of each file name.
-
-With optional REVERSE as a non-nil value, reverse the sort order."
-  (let* ((files-to-sort (copy-sequence files))
-         (sort-fn (pcase component
-                    ((pred functionp) component)
-                    ('identifier #'denote-sort-identifier-lessp)
-                    ('title #'denote-sort-title-lessp)
-                    ('keywords #'denote-sort-keywords-lessp)
-                    ('signature #'denote-sort-signature-lessp)))
-         (sorted-files (if sort-fn (sort files sort-fn) files-to-sort)))
-    (if reverse
-        (reverse sorted-files)
-      sorted-files)))
-
-(defun denote-sort-get-directory-files (files-matching-regexp 
sort-by-component &optional reverse omit-current exclude-regexp)
-  "Return sorted list of files in variable `denote-directory'.
-
-With FILES-MATCHING-REGEXP as a string limit files to those
-matching the given regular expression.
-
-With SORT-BY-COMPONENT as a symbol among `denote-sort-components',
-pass it to `denote-sort-files' to sort by the corresponding file
-name component.
-
-With optional REVERSE as a non-nil value, reverse the sort order.
-
-With optional OMIT-CURRENT, do not include the current file in
-the list.
-
-With optional EXCLUDE-REGEXP exclude the files that match the given
-regular expression.  This is done after FILES-MATCHING-REGEXP and
-OMIT-CURRENT have been applied."
-  (denote-sort-files
-   (denote-directory-files files-matching-regexp omit-current nil 
exclude-regexp)
-   sort-by-component
-   reverse))
-
-(defun denote-sort-get-links (files-matching-regexp sort-by-component 
current-file-type id-only &optional reverse exclude-regexp)
-  "Return sorted typographic list of links for FILES-MATCHING-REGEXP.
-
-With FILES-MATCHING-REGEXP as a string, match files stored in the
-variable `denote-directory'.
-
-With SORT-BY-COMPONENT as a symbol among `denote-sort-components',
-sort FILES-MATCHING-REGEXP by the given Denote file name
-component.  If SORT-BY-COMPONENT is nil or an unknown non-nil
-value, default to the identifier-based sorting.
-
-With CURRENT-FILE-TYPE as a symbol among those specified in
-the variable `denote-file-type' (or the `car' of each element in
-`denote-file-types'), format the link accordingly.  With a nil or
-unknown non-nil value, default to the Org notation.
-
-With ID-ONLY as a non-nil value, produce links that consist only
-of the identifier, thus deviating from CURRENT-FILE-TYPE.
-
-With optional REVERSE as a non-nil value, reverse the sort order.
-
-With optional EXCLUDE-REGEXP exclude the files that match the given
-regular expression.  This is done after FILES-MATCHING-REGEXP and
-OMIT-CURRENT have been applied."
-  (denote-link--prepare-links
-   (denote-sort-get-directory-files files-matching-regexp sort-by-component 
reverse exclude-regexp)
-   current-file-type
-   id-only))
-
-(defvar denote-sort-component-history nil
-  "Minibuffer history of `denote-sort-component-prompt'.")
-
-(defalias 'denote-sort--component-hist 'denote-sort-component-history
-  "Compatibility alias for `denote-sort-component-history'.")
-
-(defun denote-sort-component-prompt ()
-  "Prompt for sorting key among `denote-sort-components'."
-  (let ((default (car denote-sort-component-history)))
-    (intern
-     (completing-read
-      (format-prompt "Sort by file name component" default)
-      denote-sort-components nil :require-match
-      nil 'denote-sort-component-history default))))
-
-(defvar denote-sort-exclude-files-history nil
-  "Minibuffer history for `denote-sort-exclude-files-prompt'.")
-
-(defun denote-sort-exclude-files-prompt ()
-  "Prompt for regular expression of files to exclude."
-  ;; TODO 2024-12-03: Maybe use `read-regexp'?  We do not use it
-  ;; elsewhere, so maybe this is fine.
-  (let ((default (car denote-sort-exclude-files-history)))
-    (read-string
-     (format-prompt "Exclude files matching REGEXP" default)
-     default 'denote-sort-exclude-files-history)))
-
-(defvar-local denote-sort--dired-buffer nil
-  "Buffer object of current `denote-sort-dired'.")
-
-(defun denote-sort-dired--prompts ()
-  "Return list of prompts per `denote-sort-dired-extra-prompts'."
-  (let (sort-by-component reverse-sort exclude-rx)
-    (dolist (prompt denote-sort-dired-extra-prompts)
-      (pcase prompt
-        ('sort-by-component (setq sort-by-component 
(denote-sort-component-prompt)))
-        ('reverse-sort (setq reverse-sort (y-or-n-p "Reverse sort? ")))
-        ('exclude-regexp (setq exclude-rx 
(denote-sort-exclude-files-prompt)))))
-    (list sort-by-component reverse-sort exclude-rx)))
-
-;;;###autoload
-(defun denote-sort-dired (files-matching-regexp sort-by-component reverse 
exclude-regexp)
-  "Produce Dired buffer with sorted files from variable `denote-directory'.
-When called interactively, prompt for FILES-MATCHING-REGEXP and,
-depending on the value of the user option `denote-sort-dired-extra-prompts',
-also prompt for SORT-BY-COMPONENT, REVERSE, and EXCLUDE-REGEXP.
-
-1. FILES-MATCHING-REGEXP limits the list of Denote files to
-   those matching the provided regular expression.
-
-2. SORT-BY-COMPONENT sorts the files by their file name component (one
-   among `denote-sort-components').  If it is nil, sorting is performed
-   according to the user option `denote-sort-dired-default-sort-component',
-   falling back to the identifier.
-
-3. REVERSE is a boolean to reverse the order when it is a non-nil value.
-   If `denote-sort-dired-extra-prompts' is configured to skip this
-   prompt, then the sorting is done according to the user option
-   `denote-sort-dired-default-reverse-sort', falling back to
-   nil (i.e. no reverse sort).
-
-4. EXCLUDE-REGEXP excludes the files that match the given regular
-   expression.  This is done after FILES-MATCHING-REGEXP and
-   OMIT-CURRENT have been applied.
-
-When called from Lisp, the arguments are a string, a symbol among
-`denote-sort-components', and a non-nil value, respectively."
-  (interactive
-   (append (list (denote-files-matching-regexp-prompt)) 
(denote-sort-dired--prompts)))
-  (let ((component (or sort-by-component
-                       denote-sort-dired-default-sort-component
-                       'identifier))
-        (reverse-sort (or reverse
-                          denote-sort-dired-default-reverse-sort
-                          nil))
-        (exclude-rx (or exclude-regexp nil)))
-    (if-let* ((default-directory (denote-directory))
-              (files (denote-sort-get-directory-files files-matching-regexp 
component reverse-sort nil exclude-rx))
-              ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as
-              ;; buffer-name produces an error if the regexp contains a
-              ;; wildcard for a directory. I can reproduce this in emacs
-              ;; -Q and am not sure if it is a bug. Anyway, I will report
-              ;; it upstream, but even if it is fixed we cannot use it
-              ;; for now (whatever fix will be available for Emacs 30+).
-              (denote-sort-dired-buffer-name (format "Denote sort `%s' by 
`%s'" files-matching-regexp component))
-              (buffer-name (format "Denote sort by `%s' at %s" component 
(format-time-string "%T"))))
-        (let ((dired-buffer (dired (cons buffer-name (mapcar 
#'file-relative-name files)))))
-          (setq denote-sort--dired-buffer dired-buffer)
-          (with-current-buffer dired-buffer
-            (setq-local revert-buffer-function
-                        (lambda (&rest _)
-                          ;; FIXME 2025-01-04: Killing the buffer has
-                          ;; the unintended side effect of affecting the
-                          ;; window configuration when we call
-                          ;; `denote-update-dired-buffers'.
-                          (kill-buffer dired-buffer)
-                          (denote-sort-dired files-matching-regexp component 
reverse-sort exclude-rx))))
-          ;; Because of the above NOTE, I am printing a message.  Not
-          ;; what I want, but it is better than nothing...
-          (message denote-sort-dired-buffer-name))
-      (message "No matching files for: %s" files-matching-regexp))))
-
-(provide 'denote-sort)
-;;; denote-sort.el ends here
diff --git a/denote.el b/denote.el
index f615235497..b763415d5f 100644
--- a/denote.el
+++ b/denote.el
@@ -1464,6 +1464,312 @@ Return the absolute path to the matching file."
     ;; appropriately.
     absolute-file))
 
+;;;; The sort mechanism
+
+(defgroup denote-sort nil
+  "Sort Denote files based on a file name component."
+  :group 'denote
+  :link '(info-link "(denote) Top")
+  :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote";))
+
+(defconst denote-sort-comparison-fallback-function #'string-collate-lessp
+  "String comparison function used by `denote-sort-files' subroutines.")
+
+(defconst denote-sort-components '(title keywords signature identifier)
+  "List of sorting keys applicable for `denote-sort-files' and related.")
+
+(defcustom denote-sort-identifier-comparison-function 
denote-sort-comparison-fallback-function
+  "Function to sort the DATE/IDENTIFIER component in file names.
+The function accepts two arguments and must return a non-nil value if
+the first argument is smaller than the second one."
+  :type 'function
+  :package-version '(denote . "3.2.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-title-comparison-function 
denote-sort-comparison-fallback-function
+  "Function to sort the TITLE component in file names.
+The function accepts two arguments and must return a non-nil value if
+the first argument is smaller than the second one."
+  :type 'function
+  :package-version '(denote . "3.1.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-keywords-comparison-function 
denote-sort-comparison-fallback-function
+  "Function to sort the KEYWORDS component in file names.
+The function accepts two arguments and must return a non-nil value if
+the first argument is smaller than the second one."
+  :type 'function
+  :package-version '(denote . "3.1.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-signature-comparison-function 
denote-sort-comparison-fallback-function
+  "Function to sort the SIGNATURE component in file names.
+The function accepts two arguments and must return a non-nil value if
+the first argument is smaller than the second one."
+  :type 'function
+  :package-version '(denote . "3.1.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-dired-extra-prompts '(sort-by-component reverse-sort)
+  "Determine what `denote-sort-dired' prompts for beside a search query.
+This concerns the additional prompts issued by `denote-sort-dired' about
+whether to sort by a given file name component and to then reverse the
+sort.
+
+The value is a list of symbols, which can include the symbols
+`sort-by-component', `reverse-sort', and `exclude-regexp'.  The order is
+significant, with the leftmost symbol coming first.
+
+These symbols correspond to the following:
+
+- A choice to select the file name component to sort by.
+- A yes or no prompt on whether to reverse the sorting.
+- A string (or regular expression) of files to be excluded from the results.
+
+If the value is nil, skip all prompts.  In this scenario, the sorting is
+done according to `denote-sort-dired-default-sort-component' and
+`denote-sort-dired-default-reverse-sort'."
+  :type '(radio (const :tag "Do not prompt for anything" nil)
+                (set :tag "Available prompts" :greedy t
+                     (const :tag "Sort by file name component" 
sort-by-component)
+                     (const :tag "Reverse the sort" reverse-sort)
+                     (const :tag "Exclude files matching regexp" 
exclude-regexp)))
+  :package-version '(denote . "3.2.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-dired-default-sort-component 'identifier
+  "Set the default file name component to sort by.
+This is used only if `denote-sort-dired-extra-prompts' omits the
+minibuffer prompt for which file name component to sort by."
+  :type '(radio
+          (const :tag "Sort by identifier (default)" identifier)
+          (const :tag "Sort by title" title)
+          (const :tag "Sort by keywords" keywords)
+          (const :tag "Sort by signature" signature))
+  :package-version '(denote . "3.1.0")
+  :group 'denote-sort)
+
+(defcustom denote-sort-dired-default-reverse-sort nil
+  "If non-nil, reverse the sorting order by default.
+This is used only if `denote-sort-dired-extra-prompts' omits the
+minibuffer prompt that asks for a reverse sort or not."
+  :type 'boolean
+  :package-version '(denote . "3.1.0")
+  :group 'denote-sort)
+
+;; NOTE 2023-12-04: We can have compound sorting algorithms such as
+;; title+signature, but I want to keep this simple for the time being.
+;; Let us first hear from users to understand if there is a real need
+;; for such a feature.
+(defmacro denote-sort--define-lessp (component)
+  "Define function to sort by COMPONENT."
+  (let ((retrieve-fn (intern (format "denote-retrieve-filename-%s" component)))
+        (comparison-fn (intern (format "denote-sort-%s-comparison-function" 
component))))
+    `(defun ,(intern (format "denote-sort-%s-lessp" component)) (file1 file2)
+       ,(format
+         "Return smallest among FILE1, FILE2 based on their %s.
+The `%s' performs the comparison."
+         component comparison-fn)
+       (let* ((one (,retrieve-fn file1))
+              (two (,retrieve-fn file2))
+              (one-empty-p (or (null one) (string-empty-p one)))
+              (two-empty-p (or (null two) (string-empty-p two))))
+         (cond
+          (one-empty-p nil)
+          ((and (not one-empty-p) two-empty-p) one)
+          (t (funcall (or ,comparison-fn 
denote-sort-comparison-fallback-function) one two)))))))
+
+;; TODO 2023-12-04: Subject to the above NOTE, we can also sort by
+;; directory and by file length.
+(denote-sort--define-lessp identifier)
+(denote-sort--define-lessp title)
+(denote-sort--define-lessp keywords)
+(denote-sort--define-lessp signature)
+
+;;;###autoload
+(defun denote-sort-files (files component &optional reverse)
+  "Returned sorted list of Denote FILES.
+
+With COMPONENT as a symbol among `denote-sort-components',
+sort files based on the corresponding file name component.
+
+With COMPONENT as the symbol of a function, use it to perform the
+sorting.  In this case, the function is called with two arguments, as
+described by `sort'.
+
+With COMPONENT as a nil value keep the original date-based
+sorting which relies on the identifier of each file name.
+
+With optional REVERSE as a non-nil value, reverse the sort order."
+  (let* ((files-to-sort (copy-sequence files))
+         (sort-fn (pcase component
+                    ((pred functionp) component)
+                    ('identifier #'denote-sort-identifier-lessp)
+                    ('title #'denote-sort-title-lessp)
+                    ('keywords #'denote-sort-keywords-lessp)
+                    ('signature #'denote-sort-signature-lessp)))
+         (sorted-files (if sort-fn (sort files sort-fn) files-to-sort)))
+    (if reverse
+        (reverse sorted-files)
+      sorted-files)))
+
+(defun denote-sort-get-directory-files (files-matching-regexp 
sort-by-component &optional reverse omit-current exclude-regexp)
+  "Return sorted list of files in variable `denote-directory'.
+
+With FILES-MATCHING-REGEXP as a string limit files to those
+matching the given regular expression.
+
+With SORT-BY-COMPONENT as a symbol among `denote-sort-components',
+pass it to `denote-sort-files' to sort by the corresponding file
+name component.
+
+With optional REVERSE as a non-nil value, reverse the sort order.
+
+With optional OMIT-CURRENT, do not include the current file in
+the list.
+
+With optional EXCLUDE-REGEXP exclude the files that match the given
+regular expression.  This is done after FILES-MATCHING-REGEXP and
+OMIT-CURRENT have been applied."
+  (denote-sort-files
+   (denote-directory-files files-matching-regexp omit-current nil 
exclude-regexp)
+   sort-by-component
+   reverse))
+
+(defun denote-sort-get-links (files-matching-regexp sort-by-component 
current-file-type id-only &optional reverse exclude-regexp)
+  "Return sorted typographic list of links for FILES-MATCHING-REGEXP.
+
+With FILES-MATCHING-REGEXP as a string, match files stored in the
+variable `denote-directory'.
+
+With SORT-BY-COMPONENT as a symbol among `denote-sort-components',
+sort FILES-MATCHING-REGEXP by the given Denote file name
+component.  If SORT-BY-COMPONENT is nil or an unknown non-nil
+value, default to the identifier-based sorting.
+
+With CURRENT-FILE-TYPE as a symbol among those specified in
+the variable `denote-file-type' (or the `car' of each element in
+`denote-file-types'), format the link accordingly.  With a nil or
+unknown non-nil value, default to the Org notation.
+
+With ID-ONLY as a non-nil value, produce links that consist only
+of the identifier, thus deviating from CURRENT-FILE-TYPE.
+
+With optional REVERSE as a non-nil value, reverse the sort order.
+
+With optional EXCLUDE-REGEXP exclude the files that match the given
+regular expression.  This is done after FILES-MATCHING-REGEXP and
+OMIT-CURRENT have been applied."
+  (denote-link--prepare-links
+   (denote-sort-get-directory-files files-matching-regexp sort-by-component 
reverse exclude-regexp)
+   current-file-type
+   id-only))
+
+(defvar denote-sort-component-history nil
+  "Minibuffer history of `denote-sort-component-prompt'.")
+
+(defalias 'denote-sort--component-hist 'denote-sort-component-history
+  "Compatibility alias for `denote-sort-component-history'.")
+
+(defun denote-sort-component-prompt ()
+  "Prompt for sorting key among `denote-sort-components'."
+  (let ((default (car denote-sort-component-history)))
+    (intern
+     (completing-read
+      (format-prompt "Sort by file name component" default)
+      denote-sort-components nil :require-match
+      nil 'denote-sort-component-history default))))
+
+(defvar denote-sort-exclude-files-history nil
+  "Minibuffer history for `denote-sort-exclude-files-prompt'.")
+
+(defun denote-sort-exclude-files-prompt ()
+  "Prompt for regular expression of files to exclude."
+  ;; TODO 2024-12-03: Maybe use `read-regexp'?  We do not use it
+  ;; elsewhere, so maybe this is fine.
+  (let ((default (car denote-sort-exclude-files-history)))
+    (read-string
+     (format-prompt "Exclude files matching REGEXP" default)
+     default 'denote-sort-exclude-files-history)))
+
+(defvar-local denote-sort--dired-buffer nil
+  "Buffer object of current `denote-sort-dired'.")
+
+(defun denote-sort-dired--prompts ()
+  "Return list of prompts per `denote-sort-dired-extra-prompts'."
+  (let (sort-by-component reverse-sort exclude-rx)
+    (dolist (prompt denote-sort-dired-extra-prompts)
+      (pcase prompt
+        ('sort-by-component (setq sort-by-component 
(denote-sort-component-prompt)))
+        ('reverse-sort (setq reverse-sort (y-or-n-p "Reverse sort? ")))
+        ('exclude-regexp (setq exclude-rx 
(denote-sort-exclude-files-prompt)))))
+    (list sort-by-component reverse-sort exclude-rx)))
+
+;;;###autoload
+(defun denote-sort-dired (files-matching-regexp sort-by-component reverse 
exclude-regexp)
+  "Produce Dired buffer with sorted files from variable `denote-directory'.
+When called interactively, prompt for FILES-MATCHING-REGEXP and,
+depending on the value of the user option `denote-sort-dired-extra-prompts',
+also prompt for SORT-BY-COMPONENT, REVERSE, and EXCLUDE-REGEXP.
+
+1. FILES-MATCHING-REGEXP limits the list of Denote files to
+   those matching the provided regular expression.
+
+2. SORT-BY-COMPONENT sorts the files by their file name component (one
+   among `denote-sort-components').  If it is nil, sorting is performed
+   according to the user option `denote-sort-dired-default-sort-component',
+   falling back to the identifier.
+
+3. REVERSE is a boolean to reverse the order when it is a non-nil value.
+   If `denote-sort-dired-extra-prompts' is configured to skip this
+   prompt, then the sorting is done according to the user option
+   `denote-sort-dired-default-reverse-sort', falling back to
+   nil (i.e. no reverse sort).
+
+4. EXCLUDE-REGEXP excludes the files that match the given regular
+   expression.  This is done after FILES-MATCHING-REGEXP and
+   OMIT-CURRENT have been applied.
+
+When called from Lisp, the arguments are a string, a symbol among
+`denote-sort-components', and a non-nil value, respectively."
+  (interactive
+   (append (list (denote-files-matching-regexp-prompt)) 
(denote-sort-dired--prompts)))
+  (let ((component (or sort-by-component
+                       denote-sort-dired-default-sort-component
+                       'identifier))
+        (reverse-sort (or reverse
+                          denote-sort-dired-default-reverse-sort
+                          nil))
+        (exclude-rx (or exclude-regexp nil)))
+    (if-let* ((default-directory (denote-directory))
+              (files (denote-sort-get-directory-files files-matching-regexp 
component reverse-sort nil exclude-rx))
+              ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as
+              ;; buffer-name produces an error if the regexp contains a
+              ;; wildcard for a directory. I can reproduce this in emacs
+              ;; -Q and am not sure if it is a bug. Anyway, I will report
+              ;; it upstream, but even if it is fixed we cannot use it
+              ;; for now (whatever fix will be available for Emacs 30+).
+              (denote-sort-dired-buffer-name (format "Denote sort `%s' by 
`%s'" files-matching-regexp component))
+              (buffer-name (format "Denote sort by `%s' at %s" component 
(format-time-string "%T"))))
+        (let ((dired-buffer (dired (cons buffer-name (mapcar 
#'file-relative-name files)))))
+          (setq denote-sort--dired-buffer dired-buffer)
+          (with-current-buffer dired-buffer
+            (setq-local revert-buffer-function
+                        (lambda (&rest _)
+                          ;; FIXME 2025-01-04: Killing the buffer has
+                          ;; the unintended side effect of affecting the
+                          ;; window configuration when we call
+                          ;; `denote-update-dired-buffers'.
+                          (kill-buffer dired-buffer)
+                          (denote-sort-dired files-matching-regexp component 
reverse-sort exclude-rx))))
+          ;; Because of the above NOTE, I am printing a message.  Not
+          ;; what I want, but it is better than nothing...
+          (message denote-sort-dired-buffer-name))
+      (message "No matching files for: %s" files-matching-regexp))))
+
+(defalias 'denote-sort-dired 'denote-dired
+  "Alias for `denote-sort-dired' command.")
+
 ;;;; Keywords
 
 (defun denote-extract-keywords-from-path (path)

Reply via email to