dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

denote.el (193884B)


      1 ;;; denote.el --- Simple notes with an efficient file-naming scheme -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2022-2024  Free Software Foundation, Inc.
      4 
      5 ;; Author: Protesilaos Stavrou <info@protesilaos.com>
      6 ;; Maintainer: Protesilaos Stavrou <info@protesilaos.com>
      7 ;; URL: https://github.com/protesilaos/denote
      8 ;; Version: 2.3.3
      9 ;; Package-Requires: ((emacs "28.1"))
     10 
     11 ;; This file is NOT part of GNU Emacs.
     12 
     13 ;; This program is free software; you can redistribute it and/or modify
     14 ;; it under the terms of the GNU General Public License as published by
     15 ;; the Free Software Foundation, either version 3 of the License, or
     16 ;; (at your option) any later version.
     17 ;;
     18 ;; This program is distributed in the hope that it will be useful,
     19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     21 ;; GNU General Public License for more details.
     22 ;;
     23 ;; You should have received a copy of the GNU General Public License
     24 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     25 
     26 ;;; Commentary:
     27 
     28 ;; Denote aims to be a simple-to-use, focused-in-scope, and effective
     29 ;; note-taking and file-naming tool for Emacs.
     30 ;;
     31 ;; Denote is based on the idea that files should follow a predictable
     32 ;; and descriptive file-naming scheme.  The file name must offer a
     33 ;; clear indication of what the contents are about, without reference
     34 ;; to any other metadata.  Denote basically streamlines the creation
     35 ;; of such files or file names while providing facilities to link
     36 ;; between them (where those files are editable).
     37 ;;
     38 ;; Denote's file-naming scheme is not limited to "notes".  It can be used
     39 ;; for all types of file, including those that are not editable in Emacs,
     40 ;; such as videos.  Naming files in a consistent way makes their
     41 ;; filtering and retrieval considerably easier.  Denote provides relevant
     42 ;; facilities to rename files, regardless of file type.
     43 ;;
     44 ;; The manual describes all the technicalities about the file-naming
     45 ;; scheme, points of entry to creating new notes, commands to check
     46 ;; links between notes, and more: ;; <https://protesilaos.com/emacs/denote>.
     47 ;; If you have the info manual available, evaluate:
     48 ;;
     49 ;;    (info "(denote) Top")
     50 ;;
     51 ;; What follows is a general overview of its core core design
     52 ;; principles (again: please read the manual for the technicalities):
     53 ;;
     54 ;; * Predictability :: File names must follow a consistent and
     55 ;;   descriptive naming convention (see the manual's "The file-naming
     56 ;;   scheme").  The file name alone should offer a clear indication of
     57 ;;   what the contents are, without reference to any other metadatum.
     58 ;;   This convention is not specific to note-taking, as it is pertinent
     59 ;;   to any form of file that is part of the user's long-term storage
     60 ;;   (see the manual's "Renaming files").
     61 ;;
     62 ;; * Composability :: Be a good Emacs citizen, by integrating with other
     63 ;;   packages or built-in functionality instead of re-inventing
     64 ;;   functions such as for filtering or greping.  The author of Denote
     65 ;;   (Protesilaos, aka "Prot") writes ordinary notes in plain text
     66 ;;   (`.txt'), switching on demand to an Org file only when its expanded
     67 ;;   set of functionality is required for the task at hand (see the
     68 ;;   manual's "Points of entry").
     69 ;;
     70 ;; * Portability :: Notes are plain text and should remain portable.
     71 ;;   The way Denote writes file names, the front matter it includes in
     72 ;;   the note's header, and the links it establishes must all be
     73 ;;   adequately usable with standard Unix tools.  No need for a databse
     74 ;;   or some specialised software.  As Denote develops and this manual
     75 ;;   is fully fleshed out, there will be concrete examples on how to do
     76 ;;   the Denote-equivalent on the command-line.
     77 ;;
     78 ;; * Flexibility :: Do not assume the user's preference for a
     79 ;;   note-taking methodology.  Denote is conceptually similar to the
     80 ;;   Zettelkasten Method, which you can learn more about in this
     81 ;;   detailed introduction: <https://zettelkasten.de/introduction/>.
     82 ;;   Notes are atomic (one file per note) and have a unique identifier.
     83 ;;   However, Denote does not enforce a particular methodology for
     84 ;;   knowledge management, such as a restricted vocabulary or mutually
     85 ;;   exclusive sets of keywords.  Denote also does not check if the user
     86 ;;   writes thematically atomic notes.  It is up to the user to apply
     87 ;;   the requisite rigor and/or creativity in pursuit of their preferred
     88 ;;   workflow (see the manual's "Writing metanotes").
     89 ;;
     90 ;; * Hackability :: Denote's code base consists of small and reusable
     91 ;;   functions.  They all have documentation strings.  The idea is to
     92 ;;   make it easier for users of varying levels of expertise to
     93 ;;   understand what is going on and make surgical interventions where
     94 ;;   necessary (e.g. to tweak some formatting).  In this manual, we
     95 ;;   provide concrete examples on such user-level configurations (see
     96 ;;   the manual's "Keep a journal or diary").
     97 ;;
     98 ;; Now the important part...  "Denote" is the familiar word, though it
     99 ;; also is a play on the "note" concept.  Plus, we can come up with
    100 ;; acronyms, recursive or otherwise, of increasingly dubious utility
    101 ;; like:
    102 ;;
    103 ;; + Don't Ever Note Only The Epiphenomenal
    104 ;; + Denote Everything Neatly; Omit The Excesses
    105 ;;
    106 ;; But we'll let you get back to work.  Don't Eschew or Neglect your
    107 ;; Obligations, Tasks, and Engagements.
    108 
    109 ;;; Code:
    110 
    111 (require 'seq)
    112 (require 'xref)
    113 (require 'dired)
    114 (eval-when-compile (require 'subr-x))
    115 
    116 (defgroup denote ()
    117   "Simple notes with an efficient file-naming scheme."
    118   :group 'files
    119   :link '(info-link "(denote) Top")
    120   :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote"))
    121 
    122 ;;;; User options
    123 
    124 ;; About the autoload: (info "(elisp) File Local Variables")
    125 
    126 (define-obsolete-variable-alias
    127  'denote-user-enforced-denote-directory
    128  'denote-directory
    129  "2.3.0")
    130 
    131 ;;;###autoload (put 'denote-directory 'safe-local-variable (lambda (val) (or (stringp val) (eq val 'local) (eq val 'default-directory))))
    132 (defcustom denote-directory (expand-file-name "~/Documents/notes/")
    133   "Directory for storing personal notes.
    134 
    135 If you intend to reference this variable in Lisp, consider using
    136 the function `denote-directory' instead."
    137   :group 'denote
    138   :safe (lambda (val) (or (stringp val) (eq val 'local) (eq val 'default-directory)))
    139   :package-version '(denote . "2.0.0")
    140   :link '(info-link "(denote) Maintain separate directories for notes")
    141   :type 'directory)
    142 
    143 (defcustom denote-save-buffer-after-creation nil
    144   "Control whether commands that creeate new notes save their buffer outright.
    145 
    146 The default behaviour of commands such as `denote' (or related)
    147 is to not save the buffer they create.  This gives the user the
    148 chance to review the text before writing it to a file.  The user
    149 may choose to delete the unsaved buffer, thus not creating a new
    150 note.
    151 
    152 If this user option is set to a non-nil value, such buffers are
    153 saved automatically."
    154   :group 'denote
    155   :package-version '(denote . "2.3.0")
    156   :type 'boolean)
    157 
    158 ;;;###autoload (put 'denote-known-keywords 'safe-local-variable #'listp)
    159 (defcustom denote-known-keywords
    160   '("emacs" "philosophy" "politics" "economics")
    161   "List of strings with predefined keywords for `denote'.
    162 Also see user options: `denote-infer-keywords',
    163 `denote-sort-keywords', `denote-file-name-slug-functions'."
    164   :group 'denote
    165   :safe #'listp
    166   :package-version '(denote . "0.1.0")
    167   :type '(repeat string))
    168 
    169 ;;;###autoload (put 'denote-infer-keywords 'safe-local-variable (lambda (val) (or val (null val))))
    170 (defcustom denote-infer-keywords t
    171   "Whether to infer keywords from existing notes' file names.
    172 
    173 When non-nil, search the file names of existing notes in the
    174 variable `denote-directory' for their keyword field and extract
    175 the entries as \"inferred keywords\".  These are combined with
    176 `denote-known-keywords' and are presented as completion
    177 candidates while using `denote' and related commands
    178 interactively.
    179 
    180 If nil, refrain from inferring keywords.  The aforementioned
    181 completion prompt only shows the `denote-known-keywords'.  Use
    182 this if you want to enforce a restricted vocabulary.
    183 
    184 The user option `denote-excluded-keywords-regexp' can be used to
    185 exclude keywords that match a regular expression.
    186 
    187 Inferred keywords are specific to the value of the variable
    188 `denote-directory'.  If a silo with a local value is used, as
    189 explained in that variable's doc string, the inferred keywords
    190 are specific to the given silo.
    191 
    192 For advanced Lisp usage, the function `denote-keywords' returns
    193 the appropriate list of strings."
    194   :group 'denote
    195   :safe (lambda (val) (or val (null val)))
    196   :package-version '(denote . "0.1.0")
    197   :type 'boolean)
    198 
    199 (defcustom denote-prompts '(title keywords)
    200   "Specify the prompts followed by relevant Denote commands.
    201 
    202 Commands that prompt for user input to construct a Denote file name
    203 include, but are not limited to: `denote', `denote-signature',
    204 `denote-type', `denote-date', `denote-subdirectory',
    205 `denote-rename-file', `denote-dired-rename-files'.
    206 
    207 The value of this user option is a list of symbols, which includes any
    208 of the following:
    209 
    210 - `title': Prompt for the title of the new note.
    211 
    212 - `keywords': Prompts with completion for the keywords of the new
    213   note.  Available candidates are those specified in the user
    214   option `denote-known-keywords'.  If the user option
    215   `denote-infer-keywords' is non-nil, keywords in existing note
    216   file names are included in the list of candidates.  The
    217   `keywords' prompt uses `completing-read-multiple', meaning that
    218   it can accept multiple keywords separated by a comma (or
    219   whatever the value of `crm-separator' is).
    220 
    221 - `file-type': Prompts with completion for the file type of the
    222   new note.  Available candidates are those specified in the user
    223   option `denote-file-type'.  Without this prompt, `denote' uses
    224   the value of `denote-file-type'.
    225 
    226 - `subdirectory': Prompts with completion for a subdirectory in
    227   which to create the note.  Available candidates are the value
    228   of the user option `denote-directory' and all of its
    229   subdirectories.  Any subdirectory must already exist: Denote
    230   will not create it.
    231 
    232 - `date': Prompts for the date of the new note.  It will expect
    233   an input like 2022-06-16 or a date plus time: 2022-06-16 14:30.
    234   Without the `date' prompt, the `denote' command uses the
    235   `current-time'.  (To leverage the more sophisticated Org
    236   method, see the `denote-date-prompt-use-org-read-date'.)
    237 
    238 - `template': Prompts for a KEY among `denote-templates'.  The
    239   value of that KEY is used to populate the new note with
    240   content, which is added after the front matter.
    241 
    242 - `signature': Prompts for an arbitrary string that can be used
    243   to establish a sequential relationship between files (e.g. 1,
    244   1a, 1b, 1b1, 1b2, ...).  Signatures have no strictly defined
    245   function and are up to the user to apply as they see fit.  One
    246   use-case is to implement Niklas Luhmann's Zettelkasten system
    247   for a sequence of notes (Folgezettel).  Signatures are not
    248   included in a file's front matter.  They are reserved solely
    249   for creating a sequence in a file listing, at least for the
    250   time being.  To insert a link that includes the signature, use
    251   the command `denote-link-with-signature'.
    252 
    253 The prompts occur in the given order.
    254 
    255 If the value of this user option is nil, no prompts are used.
    256 The resulting file name will consist of an identifier (i.e. the
    257 date and time) and a supported file type extension (per
    258 `denote-file-type').
    259 
    260 Recall that Denote's standard file-naming scheme is defined as
    261 follows (read the manual for the technicalities):
    262 
    263     DATE--TITLE__KEYWORDS.EXT
    264 
    265 Depending on the inclusion of the `title', `keywords', and
    266 `signature' prompts, file names will be any of those
    267 permutations:
    268 
    269     DATE.EXT
    270     DATE--TITLE.EXT
    271     DATE__KEYWORDS.EXT
    272     DATE==SIGNATURE.EXT
    273     DATE==SIGNATURE--TITLE.EXT
    274     DATE==SIGNATURE--TITLE__KEYWORDS.EXT
    275     DATE==SIGNATURE__KEYWORDS.EXT
    276 
    277 When in doubt, always include the `title' and `keywords'
    278 prompts (the default style).
    279 
    280 Finally, this user option only affects the interactive use of the
    281 `denote' or other relevant commands (advanced users can call it from
    282 Lisp).  In Lisp usage, the behaviour is always what the caller
    283 specifies, based on the supplied arguments.
    284 
    285 Also see `denote-history-completion-in-prompts'."
    286   :group 'denote
    287   :package-version '(denote . "2.3.0")
    288   :link '(info-link "(denote) The denote-prompts option")
    289   :type '(radio (const :tag "Use no prompts" nil)
    290                 (set :tag "Available prompts" :greedy t
    291                      (const :tag "Title" title)
    292                      (const :tag "Keywords" keywords)
    293                      (const :tag "Date" date)
    294                      (const :tag "File type extension" file-type)
    295                      (const :tag "Subdirectory" subdirectory)
    296                      (const :tag "Template" template)
    297                      (const :tag "Signature" signature))))
    298 
    299 (defcustom denote-sort-keywords t
    300   "Whether to sort keywords in new files.
    301 
    302 When non-nil, the keywords of `denote' are sorted with
    303 `string-collate-lessp' regardless of the order they were inserted at the
    304 minibuffer prompt.
    305 
    306 If nil, show the keywords in their given order."
    307   :group 'denote
    308   :package-version '(denote . "0.1.0")
    309   :type 'boolean)
    310 
    311 (make-obsolete
    312  'denote-allow-multi-word-keywords
    313  'denote-file-name-letter-casing
    314  "2.1.0")
    315 
    316 (defcustom denote-file-type nil
    317   "The file type extension for new notes.
    318 
    319 By default (a nil value), the file type is that of Org mode.
    320 Though the `org' symbol can be specified for the same effect.
    321 
    322 When the value is the symbol `markdown-yaml', the file type is
    323 that of Markdown mode and the front matter uses YAML notation.
    324 Similarly, `markdown-toml' is Markdown but has TOML syntax in the
    325 front matter.
    326 
    327 When the value is `text', the file type is that of Text mode.
    328 
    329 Any other non-nil value is the same as the default.
    330 
    331 NOTE: Expert users can change the supported file-types by editing
    332 the value of `denote-file-types'.  That variable, which is not a
    333 user option, controls the behaviour of all file-type-aware
    334 functions (creating notes, renaming them, inserting front matter,
    335 formatting a link, etc.). Consult its documentation for the
    336 technicalities."
    337   :type '(choice
    338           (const :tag "Unspecified (defaults to Org)" nil)
    339           (const :tag "Org mode (default)" org)
    340           (const :tag "Markdown (YAML front matter)" markdown-yaml)
    341           (const :tag "Markdown (TOML front matter)" markdown-toml)
    342           (const :tag "Plain text" text))
    343   :package-version '(denote . "0.6.0")
    344   :group 'denote)
    345 
    346 (defcustom denote-date-format nil
    347   "Date format in the front matter (file header) of new notes.
    348 
    349 When nil (the default value), use a file-type-specific
    350 format (also check `denote-file-type'):
    351 
    352 - For Org, an inactive timestamp is used, such as [2022-06-30 Wed
    353   15:31].
    354 
    355 - For Markdown, the RFC3339 standard is applied:
    356   2022-06-30T15:48:00+03:00.
    357 
    358 - For plain text, the format is that of ISO 8601: 2022-06-30.
    359 
    360 If the value is a string, ignore the above and use it instead.
    361 The string must include format specifiers for the date.  These
    362 are described in the doc string of `format-time-string'."
    363   :type '(choice
    364           (const :tag "Use appropiate format for each file type" nil)
    365           (string :tag "Custom format for `format-time-string'"))
    366   :package-version '(denote . "0.2.0")
    367   :group 'denote)
    368 
    369 (defcustom denote-date-prompt-use-org-read-date nil
    370   "Whether to use `org-read-date' in date prompts.
    371 
    372 If non-nil, use `org-read-date'.  If nil, input the date as a
    373 string, as described in `denote'.
    374 
    375 This option is relevant when `denote-prompts' includes a `date'
    376 and/or when the user invokes the command `denote-date'."
    377   :group 'denote
    378   :package-version '(denote . "0.6.0")
    379   :type 'boolean)
    380 
    381 (defcustom denote-org-store-link-to-heading t
    382   "Determine whether `org-store-link' links to the current Org heading.
    383 
    384 When non-nil store link to the current Org heading inside the
    385 Denote file.  If the heading does not have a CUSTOM_ID, create it
    386 and include it in its PROPERTIES drawer.  If a CUSTOM_ID exists,
    387 take it as-is.
    388 
    389 Make the resulting link a combination of the `denote:' link type,
    390 pointing to the identifier of the current file, plus the value of
    391 the heading's CUSTOM_ID, such as:
    392 
    393 - [[denote:20240118T060608][Some test]]
    394 - [[denote:20240118T060608::#h:eed0fb8e-4cc7-478f][Some test::Heading text]]
    395 
    396 Both lead to the same Denote file, but the latter jumps to the
    397 heading with the given CUSTOM_ID.  Notice that the link to the
    398 heading also has a different description, which includes the
    399 heading text.
    400 
    401 The value of the CUSTOM_ID is determined by the Org user option
    402 `org-id-method'.  The sample shown above uses the default UUID
    403 infrastructure (though I deleted a few characters to not get
    404 complaints from the byte compiler about long lines in the doc
    405 string...).
    406 
    407 If this user option is set to nil, only store links to the Denote
    408 file (using its identifier), but not to the given heading.  This
    409 is what Denote was doing in versions prior to 2.3.0.
    410 
    411 What `org-store-link' does is merely collect a link.  To actually insert
    412 it, use the command `org-insert-link'.  Note that `org-capture' uses
    413 `org-store-link' internally when it needs to store a link.
    414 
    415 [ This feature only works in Org mode files, as other file types
    416   do not have a linking mechanism that handles unique identifiers
    417   for headings or other patterns to jump to.  If `org-store-link'
    418   is invoked in one such file, it captures only the Denote
    419   identifier of the file, even if this user option is set to a
    420   non-nil value.  ]"
    421   :group 'denote
    422   :package-version '(denote . "2.3.0")
    423   :type 'boolean)
    424 
    425 (defcustom denote-templates nil
    426   "Alist of content templates for new notes.
    427 A template is arbitrary text that Denote will add to a newly
    428 created note right below the front matter.
    429 
    430 Templates are expressed as a (KEY . STRING) association.
    431 
    432 - The KEY is the name which identifies the template.  It is an
    433   arbitrary symbol, such as `report', `memo', `statement'.
    434 
    435 - The STRING is ordinary text that Denote will insert as-is.  It
    436   can contain newline characters to add spacing.  The manual of
    437   Denote contains examples on how to use the `concat' function,
    438   beside writing a generic string.
    439 
    440 The user can choose a template either by invoking the command
    441 `denote-template' or by changing the user option `denote-prompts'
    442 to always prompt for a template when calling the `denote'
    443 command."
    444   :type '(alist :key-type symbol :value-type string)
    445   :package-version '(denote . "0.5.0")
    446   :link '(info-link "(denote) The denote-templates option")
    447   :group 'denote)
    448 
    449 (defcustom denote-backlinks-show-context nil
    450   "When non-nil, show link context in the backlinks buffer.
    451 
    452 The context is the line a link to the current note is found in.
    453 The context includes multiple links to the same note, if those
    454 are present.
    455 
    456 When nil, only show a simple list of file names that link to the
    457 current note."
    458   :group 'denote
    459   :package-version '(denote . "1.2.0")
    460   :type 'boolean)
    461 
    462 (defcustom denote-rename-no-confirm nil
    463   "Make renaming commands not prompt for confirmation and save buffers outright.
    464 
    465 This affects the behaviour of the commands `denote-rename-file',
    466 `denote-dired-rename-files', `denote-rename-file-using-front-matter',
    467 `denote-dired-rename-marked-files-with-keywords',
    468 `denote-dired-rename-marked-files-using-front-matter',
    469 `denote-keywords-add', `denote-keywords-remove', and any other
    470 command that builds on top of them.
    471 
    472 The default behaviour of the `denote-rename-file' command (and
    473 others like it) is to ask for an affirmative answer as a final
    474 step before changing the file name and, where relevant, inserting
    475 or updating the corresponding front matter.  It also does not
    476 save the affected file's buffer to let the user inspect and
    477 confirm the changes (such as by invoking the command
    478 `diff-buffer-with-file').
    479 
    480 With this user option bound to a non-nil value, buffers are saved
    481 as well.  The assumption is that the user who opts in to this
    482 feature is familiar with the `denote-rename-file' operation (or
    483 related) and knows it is reliable.
    484 
    485 Specialised commands that build on top of `denote-rename-file' (or related)
    486 may internally bind this user option to a non-nil value in order
    487 to perform their operation (e.g. `denote-dired-rename-files' goes
    488 through each marked Dired file, prompting for the information to
    489 use, but carries out the renaming without asking for confirmation)."
    490   :group 'denote
    491   :package-version '(denote . "2.3.0")
    492   :type 'boolean)
    493 
    494 (defcustom denote-excluded-directories-regexp nil
    495   "Regular expression of directories to exclude from all operations.
    496 Omit matching directories from file prompts and also exclude them
    497 from all functions that check the contents of the variable
    498 `denote-directory'.  The regexp needs to match only the name of
    499 the directory, not its full path.
    500 
    501 File prompts are used by several commands, such as `denote-link'
    502 and `denote-subdirectory'.
    503 
    504 Functions that check for files include `denote-directory-files'
    505 and `denote-directory-subdirectories'.
    506 
    507 The match is performed with `string-match-p'."
    508   :group 'denote
    509   :package-version '(denote . "1.2.0")
    510   :type 'string)
    511 
    512 (defcustom denote-excluded-keywords-regexp nil
    513   "Regular expression of keywords to not infer.
    514 Keywords are inferred from file names and provided at relevant
    515 prompts as completion candidates when the user option
    516 `denote-infer-keywords' is non-nil.
    517 
    518 The match is performed with `string-match-p'."
    519   :group 'denote
    520   :package-version '(denote . "1.2.0")
    521   :type 'string)
    522 
    523 (defcustom denote-after-new-note-hook nil
    524   "Normal hook that runs after the `denote' command.
    525 This also covers all convenience functions that call `denote'
    526 internally, such as `denote-signature' and `denote-type' (check
    527 the default value of the user option `denote-commands-for-new-notes')."
    528   :group 'denote
    529   :package-version '(denote . "2.1.0")
    530   :link '(info-link "(denote) Standard note creation")
    531   :type 'hook)
    532 
    533 (defcustom denote-after-rename-file-hook nil
    534   "Normal hook called after a succesful Denote rename operation.
    535 This affects the behaviour of the commands `denote-rename-file',
    536 `denote-dired-rename-files', `denote-rename-file-using-front-matter',
    537 `denote-dired-rename-marked-files-with-keywords',
    538 `denote-dired-rename-marked-files-using-front-matter',
    539 `denote-keywords-add', `denote-keywords-remove', and any other
    540 command that builds on top of them."
    541   :group 'denote
    542   :package-version '(denote . "2.3.0")
    543   :link '(info-link "(denote) Renaming files")
    544   :type 'hook)
    545 
    546 (defcustom denote-region-after-new-note-functions nil
    547   "Abnormal hook called after `denote-region'.
    548 Functions in this hook are called with two arguments,
    549 representing the beginning and end buffer positions of the region
    550 that was inserted in the new note.  These are called only if
    551 `denote-region' is invoked while a region is active.
    552 
    553 A common use-case is to call `org-insert-structure-template'
    554 after a region is inserted.  This case does not actually require
    555 the aforementioned arguments, in which case the function can
    556 simply declare them as ignored by prefixing the argument names
    557 with an underscore.  For example, the following will prompt for a
    558 structure template as soon as `denote-region' is done:
    559 
    560     (defun my-denote-region-org-structure-template (_beg _end)
    561       (when (derived-mode-p \\='org-mode)
    562         (activate-mark)
    563         (call-interactively \\='org-insert-structure-template)))
    564 
    565     (add-hook \\='denote-region-after-new-note-functions
    566               #\\='my-denote-region-org-structure-template)"
    567   :group 'denote
    568   :package-version '(denote . "2.1.0")
    569   :link '(info-link "(denote) Create a note with the region's contents")
    570   :type 'hook)
    571 
    572 (defvar denote-prompts-with-history-as-completion
    573   '(denote-title-prompt denote-signature-prompt denote-files-matching-regexp-prompt)
    574   "Prompts that conditionally perform completion against their history.
    575 
    576 These are minibuffer prompts that ordinarily accept a free form string
    577 input, as opposed to matching against a predefined set.
    578 
    579 These prompts can optionally perform completion against their own
    580 minibuffer history when the user option `denote-history-completion-in-prompts'
    581 is set to a non-nil value.")
    582 
    583 (defcustom denote-history-completion-in-prompts t
    584   "Toggle history completion in all `denote-prompts-with-history-as-completion'.
    585 
    586 When this user option is set to a non-nil value, use minibuffer history
    587 entries as completion candidates in `denote-prompts-with-history-as-completion'.
    588 Those will show previous inputs from their respective history as
    589 possible values to select, either to (i) re-insert them verbatim or (ii)
    590 with the intent to edit further (depending on the minibuffer user
    591 interface, one can select a candidate with TAB without exiting the
    592 minibuffer, as opposed to what RET normally does by selecting and
    593 exiting).
    594 
    595 When this user option is set to a nil value, all of the
    596 `denote-prompts-with-history-as-completion' do not use minibuffer
    597 completion: they just prompt for a string of characters.  Their
    598 history is still available through all the standard ways of retrieving
    599 minibuffer history, such as with the command `previous-history-element'.
    600 
    601 History completion still allows arbitrary values to be provided as
    602 input: they do not have to match the available minibuffer completion
    603 candidates.
    604 
    605 Note that some prompts, like `denote-keywords-prompt', always use
    606 minibuffer completion, due to the specifics of their data.
    607 
    608 [ Consider enabling the built-in `savehist-mode' to persist minibuffer
    609   histories between sessions.]
    610 
    611 Also see `denote-prompts'."
    612   :type 'boolean
    613   :package-version '(denote . "2.3.0")
    614   :group 'denote)
    615 
    616 (defcustom denote-commands-for-new-notes
    617   '(denote
    618     denote-date
    619     denote-subdirectory
    620     denote-template
    621     denote-type
    622     denote-signature)
    623   "List of commands for `denote-command-prompt' that create a new note.
    624 These are used by commands such as `denote-open-or-create-with-command'
    625 and `denote-link-after-creating-with-command'."
    626   :group 'denote
    627   :package-version '(denote . "2.1.0")
    628   :link '(info-link "(denote) Choose which commands to prompt for")
    629   :type '(repeat symbol))
    630 
    631 (defcustom denote-file-name-slug-functions
    632   '((title . denote-sluggify-title)
    633     (signature . denote-sluggify-signature)
    634     (keyword . denote-sluggify-keyword))
    635   "Specify the method Denote uses to format the components of the file name.
    636 
    637 The value is an alist where each element is a cons cell of the
    638 form (COMPONENT . METHOD).
    639 
    640 - The COMPONENT is an unquoted symbol among `title', `signature',
    641   `keyword' (notice the absence of `s', see below), which
    642   refers to the corresponding component of the file name.
    643 
    644 - The METHOD is the function to be used to format the given
    645   component.  This function should take a string as its parameter
    646   and return the string formatted for the file name.  In the case
    647   of the `keyword' component, the function receives a SINGLE
    648   string representing a single keyword and return it formatted
    649   for the file name.  Joining the keywords together is handled by
    650   Denote.
    651 
    652 Note that the `keyword' function is also applied to the keywords
    653 of the front matter.
    654 
    655 By default, if a function is not specified for a component, we
    656 use `denote-sluggify-title', `denote-sluggify-keyword' and
    657 `denote-sluggify-signature'.
    658 
    659 Remember that deviating from the default file-naming scheme of Denote
    660 will make things harder to search in the future, as files can/will have
    661 permutations that create uncertainty.  The sluggification scheme and
    662 concomitant restrictions we impose by default are there for a very good
    663 reason: they are the distillation of years of experience.  Here we give
    664 you what you wish, but bear in mind it may not be what you need.  You
    665 have been warned."
    666   :group 'denote
    667   :package-version '(denote . "2.3.0")
    668   :link '(info-link "(denote) User-defined sluggification of file name components")
    669   :type '(alist :key (choice (const title)
    670                              (const signature)
    671                              (const keyword))
    672                 :value function))
    673 
    674 (make-obsolete
    675  'denote-file-name-letter-casing
    676  'denote-file-name-slug-functions
    677  "2.3.0")
    678 
    679 (defcustom denote-link-description-function #'denote-link-description-with-signature-and-title
    680   "Function to create the description of links.
    681 
    682 The function specified takes a FILE argument and returns the description
    683 as a string.
    684 
    685 By default, the title of the file is returned as the description.  If
    686 the file has a signature, it is prepended to the title."
    687   :group 'denote
    688   :type '(choice
    689           (function :tag "Link to title and include signature, if present" denote-link-description-with-signature-and-title)
    690           (function :tag "Custom function like `denote-link-description-with-signature-and-title'"))
    691   :package-version '(denote . "2.3.0"))
    692 
    693 ;;;; Main variables
    694 
    695 ;; For character classes, evaluate: (info "(elisp) Char Classes")
    696 
    697 (defconst denote-id-format "%Y%m%dT%H%M%S"
    698   "Format of ID prefix of a note's filename.
    699 The note's ID is derived from the date and time of its creation.")
    700 
    701 (defconst denote-id-regexp "\\([0-9]\\{8\\}\\)\\(T[0-9]\\{6\\}\\)"
    702   "Regular expression to match `denote-id-format'.")
    703 
    704 (defconst denote-signature-regexp "==\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
    705   "Regular expression to match the SIGNATURE field in a file name.")
    706 
    707 (defconst denote-title-regexp "--\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
    708   "Regular expression to match the TITLE field in a file name.")
    709 
    710 (defconst denote-keywords-regexp "__\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
    711   "Regular expression to match the KEYWORDS field in a file name.")
    712 
    713 (defconst denote-excluded-punctuation-regexp "[][{}!@#$%^&*()=+'\"?,.\|;:~`‘’“”/]*"
    714   "Punctionation that is removed from file names.
    715 We consider those characters illegal for our purposes.")
    716 
    717 (defvar denote-excluded-punctuation-extra-regexp nil
    718   "Additional punctuation that is removed from file names.
    719 This variable is for advanced users who need to extend the
    720 `denote-excluded-punctuation-regexp'.  Once we have a better
    721 understanding of what we should be omitting, we will update
    722 things accordingly.")
    723 
    724 ;;;; File helper functions
    725 
    726 (defun denote--completion-table (category candidates)
    727   "Pass appropriate metadata CATEGORY to completion CANDIDATES."
    728   (lambda (string pred action)
    729     (if (eq action 'metadata)
    730         `(metadata (category . ,category))
    731       (complete-with-action action candidates string pred))))
    732 
    733 (defun denote--completion-table-no-sort (category candidates)
    734   "Pass appropriate metadata CATEGORY to completion CANDIDATES.
    735 Like `denote--completion-table' but also disable sorting."
    736   (lambda (string pred action)
    737     (if (eq action 'metadata)
    738         `(metadata (category . ,category)
    739                    (display-sort-function . ,#'identity))
    740       (complete-with-action action candidates string pred))))
    741 
    742 (defun denote--default-directory-is-silo-p ()
    743   "Return path to silo if `default-directory' is a silo."
    744   (when-let ((dir-locals (dir-locals-find-file default-directory))
    745              ((alist-get 'denote-directory dir-local-variables-alist)))
    746     (cond
    747      ((listp dir-locals)
    748       (car dir-locals))
    749      ((stringp dir-locals)
    750       dir-locals))))
    751 
    752 (defun denote--make-denote-directory ()
    753   "Make the variable `denote-directory' and its parents, if needed."
    754   (when (not (file-directory-p denote-directory))
    755     (make-directory denote-directory :parents)))
    756 
    757 (defun denote-directory ()
    758   "Return path of variable `denote-directory' as a proper directory.
    759 Custom Lisp code can `let' bind the variable `denote-directory'
    760 to override what this function returns."
    761   ;; NOTE 2024-02-09: We may want to remove this condition eventually.
    762   ;; The reason is that we want to stop supporting the dir-local
    763   ;; values of `default-directory' or `local' in favour of just
    764   ;; specifying a string.  I don't think we can delete this altogether
    765   ;; though, as it will break existing configurations.
    766   (if-let (((or (eq denote-directory 'default-directory) (eq denote-directory 'local)))
    767            (silo-dir (denote--default-directory-is-silo-p)))
    768       silo-dir
    769     (let ((denote-directory (file-name-as-directory (expand-file-name denote-directory))))
    770       (denote--make-denote-directory)
    771       denote-directory)))
    772 
    773 (defun denote--slug-no-punct (str &optional extra-characters)
    774   "Remove punctuation from STR.
    775 Concretely, replace with an empty string anything that matches
    776 the `denote-excluded-punctuation-regexp' and
    777 `denote-excluded-punctuation-extra-regexp'.
    778 
    779 EXTRA-CHARACTERS is an optional string that has the same meaning
    780 as the aforementioned variables."
    781   (dolist (regexp (list denote-excluded-punctuation-regexp
    782                         denote-excluded-punctuation-extra-regexp
    783                         extra-characters))
    784     (when (stringp regexp)
    785       (setq str (replace-regexp-in-string regexp "" str))))
    786   str)
    787 
    788 (defun denote--slug-no-punct-for-signature (str &optional extra-characters)
    789   "Remove punctuation (except = signs) from STR.
    790 
    791 This works the same way as `denote--slug-no-punct', except that =
    792 signs are not removed from STR.
    793 
    794 EXTRA-CHARACTERS is an optional string.  See
    795 `denote--slug-no-punct' for its documentation."
    796   (dolist (regexp (list denote-excluded-punctuation-regexp
    797                         denote-excluded-punctuation-extra-regexp
    798                         extra-characters))
    799     (when (stringp regexp)
    800       (setq str (replace-regexp-in-string (string-replace "=" "" regexp) "" str))))
    801   str)
    802 
    803 (defun denote--slug-hyphenate (str)
    804   "Replace spaces and underscores with hyphens in STR.
    805 Also replace multiple hyphens with a single one and remove any
    806 leading and trailing hyphen."
    807   (replace-regexp-in-string
    808    "^-\\|-$" ""
    809    (replace-regexp-in-string
    810     "-\\{2,\\}" "-"
    811     (replace-regexp-in-string "_\\|\s+" "-" str))))
    812 
    813 (defun denote--remove-dot-characters (str)
    814   "Remove the dot character from STR."
    815   (replace-regexp-in-string "\\." "" str))
    816 
    817 (defun denote--trim-right-token-characters (str)
    818   "Remove =, - and _ from the end of STR."
    819   (string-trim-right str "[=_-]+"))
    820 
    821 (defun denote--replace-consecutive-token-characters (str)
    822   "Replace consecutive characters with a single one in STR.
    823 Spaces, underscores and equal signs are replaced with a single
    824 one in str."
    825   (replace-regexp-in-string
    826    "-\\{2,\\}" "-"
    827    (replace-regexp-in-string
    828     "_\\{2,\\}" "_"
    829     (replace-regexp-in-string
    830      "=\\{2,\\}" "=" str))))
    831 
    832 (defun denote-sluggify (component str)
    833   "Make STR an appropriate slug for file name COMPONENT.
    834 
    835 Apply the function specified in `denote-file-name-slug-function'
    836 to COMPONENT which is one of `title', `signature', `keyword'.  If
    837 the resulting string still contains consecutive -,_ or =, they
    838 are replaced by a single occurence of the character.  If
    839 COMPONENT is `keyword', remove underscores from STR as they are
    840 used as the keywords separator in file names."
    841   (let* ((slug-function (alist-get component denote-file-name-slug-functions))
    842          (str-slug (cond ((eq component 'title)
    843                           (funcall (or slug-function #'denote-sluggify-title) str))
    844                          ((eq component 'keyword)
    845                           (replace-regexp-in-string
    846                            "_" ""
    847                            (funcall (or slug-function #'denote-sluggify-keyword) str)))
    848                          ((eq component 'signature)
    849                           (funcall (or slug-function #'denote-sluggify-signature) str)))))
    850     (denote--trim-right-token-characters
    851      (denote--replace-consecutive-token-characters
    852       (denote--remove-dot-characters str-slug)))))
    853 
    854 (make-obsolete
    855  'denote-letter-case
    856  'denote-sluggify
    857  "2.3.0")
    858 
    859 (defun denote--slug-put-equals (str)
    860   "Replace spaces and underscores with equals signs in STR.
    861 Also replace multiple equals signs with a single one and remove
    862 any leading and trailing signs."
    863   (replace-regexp-in-string
    864    "^=\\|=$" ""
    865    (replace-regexp-in-string
    866     "=\\{2,\\}" "="
    867     (replace-regexp-in-string "_\\|\s+" "=" str))))
    868 
    869 (defun denote-sluggify-title (str)
    870   "Make STR an appropriate slug for title."
    871   (downcase (denote--slug-hyphenate (denote--slug-no-punct str))))
    872 
    873 (defun denote-sluggify-signature (str)
    874   "Make STR an appropriate slug for signature."
    875   (downcase (denote--slug-put-equals (denote--slug-no-punct-for-signature str "-+"))))
    876 
    877 (defun denote-sluggify-keyword (str)
    878   "Sluggify STR while joining separate words."
    879   (downcase
    880    (replace-regexp-in-string
    881     "-" ""
    882     (denote--slug-hyphenate (denote--slug-no-punct str)))))
    883 
    884 (make-obsolete
    885  'denote-sluggify-and-join
    886  'denote-sluggify-keyword
    887  "2.3.0")
    888 
    889 (defun denote-sluggify-keywords (keywords)
    890   "Sluggify KEYWORDS, which is a list of strings."
    891   (mapcar (lambda (keyword)
    892             (denote-sluggify 'keyword keyword))
    893           keywords))
    894 
    895 (defun denote--file-empty-p (file)
    896   "Return non-nil if FILE is empty."
    897   (zerop (or (file-attribute-size (file-attributes file)) 0)))
    898 
    899 (defun denote-file-has-identifier-p (file)
    900   "Return non-nil if FILE has a Denote identifier."
    901   (denote-retrieve-filename-identifier file))
    902 
    903 (defun denote-file-has-supported-extension-p (file)
    904   "Return non-nil if FILE has supported extension.
    905 Also account for the possibility of an added .gpg suffix.
    906 Supported extensions are those implied by `denote-file-type'."
    907   (seq-some (lambda (e)
    908               (string-suffix-p e file))
    909             (denote-file-type-extensions-with-encryption)))
    910 
    911 (defun denote-filename-is-note-p (filename)
    912   "Return non-nil if FILENAME is a valid name for a Denote note.
    913 For our purposes, its path must be part of the variable
    914 `denote-directory', it must have a Denote identifier in its name,
    915 and use one of the extensions implied by `denote-file-type'."
    916   (and (string-prefix-p (denote-directory) (expand-file-name filename))
    917        (denote-file-has-identifier-p filename)
    918        (denote-file-has-supported-extension-p filename)))
    919 
    920 (defun denote-file-is-note-p (file)
    921   "Return non-nil if FILE is an actual Denote note.
    922 For our purposes, a note must not be a directory, must satisfy
    923 `file-regular-p' and `denote-filename-is-note-p'."
    924   (and (not (file-directory-p file))
    925        (file-regular-p file)
    926        (denote-filename-is-note-p file)))
    927 
    928 (defun denote-file-has-signature-p (file)
    929   "Return non-nil if FILE has a Denote identifier."
    930   (denote-retrieve-filename-signature file))
    931 
    932 (make-obsolete 'denote-file-directory-p nil "2.0.0")
    933 
    934 (defun denote--file-regular-writable-p (file)
    935   "Return non-nil if FILE is regular and writable."
    936   (and (file-regular-p file)
    937        (file-writable-p file)))
    938 
    939 (defun denote-file-is-writable-and-supported-p (file)
    940   "Return non-nil if FILE is writable and has supported extension."
    941   (and (denote--file-regular-writable-p file)
    942        (denote-file-has-supported-extension-p file)))
    943 
    944 (defun denote-get-file-name-relative-to-denote-directory (file)
    945   "Return name of FILE relative to the variable `denote-directory'.
    946 FILE must be an absolute path."
    947   (when-let ((dir (denote-directory))
    948              ((file-name-absolute-p file))
    949              (file-name (expand-file-name file))
    950              ((string-prefix-p dir file-name)))
    951     (substring-no-properties file-name (length dir))))
    952 
    953 (defun denote-extract-id-from-string (string)
    954   "Return existing Denote identifier in STRING, else nil."
    955   (when (string-match denote-id-regexp string)
    956     (match-string-no-properties 0 string)))
    957 
    958 (define-obsolete-function-alias
    959   'denote--default-dir-has-denote-prefix
    960   'denote--dir-in-denote-directory-p
    961   "2.1.0")
    962 
    963 (defun denote--exclude-directory-regexp-p (file)
    964   "Return non-nil if FILE matches `denote-excluded-directories-regexp'."
    965   (and denote-excluded-directories-regexp
    966        (string-match-p denote-excluded-directories-regexp file)))
    967 
    968 (defun denote--directory-files-recursively-predicate (file)
    969   "Predicate used by `directory-files-recursively' on FILE.
    970 
    971 Return t if FILE is valid, else return nil."
    972   (let ((rel (denote-get-file-name-relative-to-denote-directory file)))
    973     (cond
    974      ((string-match-p "\\`\\." rel) nil)
    975      ((string-match-p "/\\." rel) nil)
    976      ((denote--exclude-directory-regexp-p rel) nil)
    977      ((file-readable-p file)))))
    978 
    979 (defun denote--directory-all-files-recursively ()
    980   "Return list of all files in variable `denote-directory'.
    981 Avoids traversing dotfiles (unconditionally) and whatever matches
    982 `denote-excluded-directories-regexp'."
    983   (directory-files-recursively
    984    (denote-directory)
    985    directory-files-no-dot-files-regexp
    986    :include-directories
    987    #'denote--directory-files-recursively-predicate
    988    :follow-symlinks))
    989 
    990 (defun denote--directory-get-files ()
    991   "Return list with full path of valid files in variable `denote-directory'.
    992 Consider files that satisfy `denote-file-has-identifier-p' and
    993 are not backups."
    994   (mapcar
    995    #'expand-file-name
    996    (seq-filter
    997     (lambda (file)
    998       (and (denote-file-has-identifier-p file)
    999            (not (backup-file-name-p file))))
   1000       (denote--directory-all-files-recursively))))
   1001 
   1002 (defun denote-directory-files (&optional files-matching-regexp omit-current text-only)
   1003   "Return list of absolute file paths in variable `denote-directory'.
   1004 
   1005 Files only need to have an identifier.  The return value may thus
   1006 include file types that are not implied by `denote-file-type'.
   1007 
   1008 Remember that the variable `denote-directory' accepts a dir-local
   1009 value, as explained in its doc string.
   1010 
   1011 With optional FILES-MATCHING-REGEXP, restrict files to those
   1012 matching the given regular expression.
   1013 
   1014 With optional OMIT-CURRENT as a non-nil value, do not include the
   1015 current Denote file in the returned list.
   1016 
   1017 With optional TEXT-ONLY as a non-nil value, limit the results to
   1018 text files that satisfy `denote-file-is-note-p'."
   1019   (let ((files (denote--directory-get-files)))
   1020     (when (and omit-current buffer-file-name (denote-file-has-identifier-p buffer-file-name))
   1021       (setq files (delete buffer-file-name files)))
   1022     (when files-matching-regexp
   1023       (setq files (seq-filter
   1024                    (lambda (f)
   1025                      (string-match-p files-matching-regexp (denote-get-file-name-relative-to-denote-directory f)))
   1026                    files)))
   1027     (when text-only
   1028       (setq files (seq-filter #'denote-file-is-note-p files)))
   1029     files))
   1030 
   1031 ;; NOTE 2023-11-30: We are declaring `denote-directory-text-only-files'
   1032 ;; obsolete, though we keep it around for the foreseeable future.  It
   1033 ;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that
   1034 ;; happens.
   1035 (make-obsolete 'denote-directory-text-only-files 'denote-directory-files "2.2.0")
   1036 
   1037 (defun denote-directory-text-only-files ()
   1038   "Return list of text files in variable `denote-directory'.
   1039 Filter `denote-directory-files' using `denote-file-is-note-p'."
   1040   (denote-directory-files nil nil :text-only))
   1041 
   1042 (defun denote-directory-subdirectories ()
   1043   "Return list of subdirectories in variable `denote-directory'.
   1044 Omit dotfiles (such as .git) unconditionally.  Also exclude
   1045 whatever matches `denote-excluded-directories-regexp'."
   1046   (seq-remove
   1047    (lambda (filename)
   1048      (let ((rel (denote-get-file-name-relative-to-denote-directory filename)))
   1049        (or (not (file-directory-p filename))
   1050            (string-match-p "\\`\\." rel)
   1051            (string-match-p "/\\." rel)
   1052            (denote--exclude-directory-regexp-p rel))))
   1053    (denote--directory-all-files-recursively)))
   1054 
   1055 (define-obsolete-variable-alias
   1056   'denote--encryption-file-extensions
   1057   'denote-encryption-file-extensions
   1058   "2.0.0")
   1059 
   1060 ;; TODO 2023-01-24: Perhaps there is a good reason to make this a user
   1061 ;; option, but I am keeping it as a generic variable for now.
   1062 (defvar denote-encryption-file-extensions '(".gpg" ".age")
   1063   "List of strings specifying file extensions for encryption.")
   1064 
   1065 (define-obsolete-function-alias
   1066   'denote--extensions-with-encryption
   1067   'denote-file-type-extensions-with-encryption
   1068   "2.0.0")
   1069 
   1070 (defun denote-file-type-extensions-with-encryption ()
   1071   "Derive `denote-file-type-extensions' plus `denote-encryption-file-extensions'."
   1072   (let ((file-extensions (denote-file-type-extensions))
   1073         all)
   1074     (dolist (ext file-extensions)
   1075       (dolist (enc denote-encryption-file-extensions)
   1076         (push (concat ext enc) all)))
   1077     (append file-extensions all)))
   1078 
   1079 (defun denote-get-file-extension (file)
   1080   "Return extension of FILE with dot included.
   1081 Account for `denote-encryption-file-extensions'.  In other words,
   1082 return something like .org.gpg if it is part of the file, else
   1083 return .org."
   1084   (let ((outer-extension (file-name-extension file :period)))
   1085     (if-let (((member outer-extension denote-encryption-file-extensions))
   1086              (file (file-name-sans-extension file))
   1087              (inner-extension (file-name-extension file :period)))
   1088         (concat inner-extension outer-extension)
   1089       outer-extension)))
   1090 
   1091 (defun denote-get-file-extension-sans-encryption (file)
   1092   "Return extension of FILE with dot included and without the encryption part.
   1093 Build on top of `denote-get-file-extension' though always return
   1094 something like .org even if the actual file extension is
   1095 .org.gpg."
   1096   (let ((extension (denote-get-file-extension file)))
   1097     (if (string-match (regexp-opt denote-encryption-file-extensions) extension)
   1098         (substring extension 0 (match-beginning 0))
   1099       extension)))
   1100 
   1101 (defun denote-get-path-by-id (id)
   1102   "Return absolute path of ID string in `denote-directory-files'."
   1103   (let ((files
   1104          (seq-filter
   1105           (lambda (file)
   1106             (string-prefix-p id (file-name-nondirectory file)))
   1107           (denote-directory-files))))
   1108     (if (length< files 2)
   1109         (car files)
   1110       (seq-find
   1111        (lambda (file)
   1112          (let ((file-extension (denote-get-file-extension file)))
   1113            (and (denote-file-is-note-p file)
   1114                 (or (string= (denote--file-extension denote-file-type)
   1115                              file-extension)
   1116                     (string= ".org" file-extension)
   1117                     (member file-extension (denote-file-type-extensions))))))
   1118        files))))
   1119 
   1120 (defun denote-get-relative-path-by-id (id &optional directory)
   1121   "Return relative path of ID string in `denote-directory-files'.
   1122 The path is relative to DIRECTORY (default: ‘default-directory’)."
   1123   (file-relative-name (denote-get-path-by-id id) directory))
   1124 
   1125 ;; NOTE 2023-11-30: We are declaring `denote-directory-files-matching-regexp'
   1126 ;; obsolete, though we keep it around for the foreseeable future.  It
   1127 ;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that
   1128 ;; happens.
   1129 (make-obsolete 'denote-directory-files-matching-regexp 'denote-directory-files "2.2.0")
   1130 
   1131 (defun denote-directory-files-matching-regexp (regexp)
   1132   "Return list of files matching REGEXP in `denote-directory-files'."
   1133   (denote-directory-files regexp))
   1134 
   1135 ;; NOTE 2023-11-30: We are declaring `denote-all-files' obsolete,
   1136 ;; though we keep it around for the foreseeable future.  It WILL BE
   1137 ;; REMOVED ahead of version 3.0.0 of Denote, whenever that happens.
   1138 (make-obsolete 'denote-all-files 'denote-directory-files "2.2.0")
   1139 
   1140 (defun denote-all-files (&optional omit-current)
   1141   "Return the list of Denote files in variable `denote-directory'.
   1142 With optional OMIT-CURRENT, do not include the current Denote
   1143 file in the returned list."
   1144   (denote-directory-files nil omit-current nil))
   1145 
   1146 (defvar denote-file-history nil
   1147   "Minibuffer history of `denote-file-prompt'.")
   1148 
   1149 (defalias 'denote--file-history 'denote-file-history
   1150   "Compatibility alias for `denote-file-history'.")
   1151 
   1152 ;; NOTE 2024-02-29: Based on `project--read-file-cpd-relative' from
   1153 ;; the built-in project.el
   1154 (defun denote-file-prompt (&optional files-matching-regexp prompt-text)
   1155   "Prompt for file with identifier in variable `denote-directory'.
   1156 With optional FILES-MATCHING-REGEXP, filter the candidates per
   1157 the given regular expression.
   1158 
   1159 With optional PROMPT-TEXT, use it instead of the default call to
   1160 \"Select NOTE\"."
   1161   (when-let ((all-files (denote-directory-files files-matching-regexp :omit-current)))
   1162     (let* ((common-parent-directory
   1163             (let ((common-prefix (try-completion "" all-files)))
   1164               (if (> (length common-prefix) 0)
   1165                   (file-name-directory common-prefix))))
   1166            (cpd-length (length common-parent-directory))
   1167            (prompt-prefix (or prompt-text "Select FILE"))
   1168            (prompt (if (zerop cpd-length)
   1169                        (format "%s: " prompt-prefix)
   1170                      (format "%s in %s: " prompt-prefix common-parent-directory)))
   1171            (included-cpd (when (member common-parent-directory all-files)
   1172                            (setq all-files
   1173                                  (delete common-parent-directory all-files))
   1174                            t))
   1175            (substrings (mapcar (lambda (s) (substring s cpd-length)) all-files))
   1176            (_ (when included-cpd
   1177                 (setq substrings (cons "./" substrings))))
   1178            (new-collection (denote--completion-table 'file substrings))
   1179            (relname (completing-read prompt new-collection nil nil nil 'denote-file-history))
   1180            (absname (expand-file-name relname common-parent-directory)))
   1181       ;; NOTE 2024-02-29: This delete and add feels awkward.  I wish
   1182       ;; we could tell `completing-read' to just leave this up to us.
   1183       (setq denote-file-history (delete relname denote-file-history))
   1184       (add-to-history 'denote-file-history absname)
   1185       absname)))
   1186 
   1187 ;;;; Keywords
   1188 
   1189 (defun denote-extract-keywords-from-path (path)
   1190   "Extract keywords from PATH and return them as a list of strings.
   1191 PATH must be a Denote-style file name where keywords are prefixed
   1192 with an underscore.
   1193 
   1194 If PATH has no such keywords, return nil."
   1195   (when-let ((kws (denote-retrieve-filename-keywords path)))
   1196     (split-string kws "_")))
   1197 
   1198 (defun denote--inferred-keywords ()
   1199   "Extract keywords from `denote-directory-files'.
   1200 This function returns duplicates.  The `denote-keywords' is the
   1201 one that doesn't."
   1202   (let ((kw (mapcan #'denote-extract-keywords-from-path (denote-directory-files))))
   1203     (if-let ((regexp denote-excluded-keywords-regexp))
   1204         (seq-remove (apply-partially #'string-match-p regexp) kw)
   1205       kw)))
   1206 
   1207 (defun denote-keywords ()
   1208   "Return appropriate list of keyword candidates.
   1209 If `denote-infer-keywords' is non-nil, infer keywords from
   1210 existing notes and combine them into a list with
   1211 `denote-known-keywords'.  Else use only the latter.
   1212 
   1213 Inferred keywords are filtered by the user option
   1214 `denote-excluded-keywords-regexp'."
   1215   (delete-dups
   1216    (if denote-infer-keywords
   1217        (append (denote--inferred-keywords) denote-known-keywords)
   1218      denote-known-keywords)))
   1219 
   1220 (defun denote-convert-file-name-keywords-to-crm (string)
   1221   "Make STRING with keywords readable by `completing-read-multiple'.
   1222 STRING consists of underscore-separated words, as those appear in
   1223 the keywords component of a Denote file name.  STRING is the same
   1224 as the return value of `denote-retrieve-filename-keywords'."
   1225   (string-join (split-string string "_" :omit-nulls "_") ","))
   1226 
   1227 (defvar denote-keyword-history nil
   1228   "Minibuffer history of inputted keywords.")
   1229 
   1230 (defalias 'denote--keyword-history 'denote-keyword-history
   1231   "Compatibility alias for `denote-keyword-history'.")
   1232 
   1233 (defun denote--keywords-crm (keywords &optional prompt initial)
   1234   "Use `completing-read-multiple' for KEYWORDS.
   1235 With optional PROMPT, use it instead of a generic text for file
   1236 keywords.  With optional INITIAL, add it to the minibuffer as
   1237 initial input."
   1238   (delete-dups
   1239    (completing-read-multiple
   1240     (format-prompt (or prompt "New file KEYWORDS") nil)
   1241     keywords nil nil initial 'denote-keyword-history)))
   1242 
   1243 (defun denote-keywords-prompt (&optional prompt-text initial-keywords)
   1244   "Prompt for one or more keywords.
   1245 Read entries as separate when they are demarcated by the
   1246 `crm-separator', which typically is a comma.
   1247 
   1248 With optional PROMPT-TEXT, use it to prompt the user for
   1249 keywords.  Else use a generic prompt.  With optional
   1250 INITIAL-KEYWORDS use them as the initial minibuffer text.
   1251 
   1252 Return an empty list if the minibuffer input is empty."
   1253   (denote--keywords-crm (denote-keywords) prompt-text initial-keywords))
   1254 
   1255 (defun denote-keywords-sort (keywords)
   1256   "Sort KEYWORDS if `denote-sort-keywords' is non-nil.
   1257 KEYWORDS is a list of strings, per `denote-keywords-prompt'."
   1258   (if denote-sort-keywords
   1259       (sort (copy-sequence keywords) #'string-collate-lessp)
   1260     keywords))
   1261 
   1262 (define-obsolete-function-alias
   1263   'denote--keywords-combine
   1264   'denote-keywords-combine
   1265   "2.1.0")
   1266 
   1267 (defun denote-keywords-combine (keywords)
   1268   "Combine KEYWORDS list of strings into a single string.
   1269 Keywords are separated by the underscore character, per the
   1270 Denote file-naming scheme."
   1271   (string-join keywords "_"))
   1272 
   1273 (defun denote--keywords-add-to-history (keywords)
   1274   "Append KEYWORDS to `denote-keyword-history'."
   1275   (mapc
   1276    (lambda (kw)
   1277      (add-to-history 'denote-keyword-history kw))
   1278    (delete-dups keywords)))
   1279 
   1280 ;;;; File types
   1281 
   1282 (defvar denote-org-front-matter
   1283   "#+title:      %s
   1284 #+date:       %s
   1285 #+filetags:   %s
   1286 #+identifier: %s
   1287 \n"
   1288   "Org front matter.
   1289 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
   1290 ID.  Advanced users are advised to consult Info node `(denote)
   1291 Change the front matter format'.")
   1292 
   1293 (defvar denote-yaml-front-matter
   1294   "---
   1295 title:      %s
   1296 date:       %s
   1297 tags:       %s
   1298 identifier: %S
   1299 ---\n\n"
   1300   "YAML (Markdown) front matter.
   1301 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
   1302 ID.  Advanced users are advised to consult Info node `(denote)
   1303 Change the front matter format'.")
   1304 
   1305 (defvar denote-toml-front-matter
   1306   "+++
   1307 title      = %s
   1308 date       = %s
   1309 tags       = %s
   1310 identifier = %S
   1311 +++\n\n"
   1312   "TOML (Markdown) front matter.
   1313 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
   1314 ID.  Advanced users are advised to consult Info node `(denote)
   1315 Change the front matter format'.")
   1316 
   1317 (defvar denote-text-front-matter
   1318   "title:      %s
   1319 date:       %s
   1320 tags:       %s
   1321 identifier: %s
   1322 ---------------------------\n\n"
   1323   "Plain text front matter.
   1324 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
   1325 ID.  Advanced users are advised to consult Info node `(denote)
   1326 Change the front matter format'.")
   1327 
   1328 (define-obsolete-function-alias
   1329   'denote-surround-with-quotes
   1330   'denote-format-string-for-md-front-matter
   1331   "2.3.0")
   1332 
   1333 (defun denote-format-string-for-md-front-matter (s)
   1334   "Surround string S with quotes.
   1335 If S is not a string, return a literal emptry string.
   1336 
   1337 This can be used in `denote-file-types' to format front mattter."
   1338   (if (stringp s)
   1339       (format "%S" s)
   1340     "\"\""))
   1341 
   1342 (defun denote-trim-whitespace (s)
   1343   "Trim whitespace around string S.
   1344 This can be used in `denote-file-types' to format front mattter."
   1345   (string-trim s))
   1346 
   1347 (defun denote--trim-quotes (s)
   1348   "Trim quotes around string S."
   1349   (let ((trims "[\"']+"))
   1350     (string-trim s trims trims)))
   1351 
   1352 (defun denote-trim-whitespace-then-quotes (s)
   1353   "Trim whitespace then quotes around string S.
   1354 This can be used in `denote-file-types' to format front mattter."
   1355   (denote--trim-quotes (denote-trim-whitespace s)))
   1356 
   1357 (defun denote-format-string-for-org-front-matter (s)
   1358   "Return string S as-is for Org or plain text front matter.
   1359 If S is not a string, return an empty string."
   1360   (if (stringp s) s ""))
   1361 
   1362 (defun denote-format-keywords-for-md-front-matter (keywords)
   1363   "Format front matter KEYWORDS for markdown file type.
   1364 KEYWORDS is a list of strings.  Consult the `denote-file-types'
   1365 for how this is used."
   1366   (format "[%s]" (mapconcat (lambda (k) (format "%S" k)) keywords ", ")))
   1367 
   1368 (defun denote-format-keywords-for-text-front-matter (keywords)
   1369   "Format front matter KEYWORDS for text file type.
   1370 KEYWORDS is a list of strings.  Consult the `denote-file-types'
   1371 for how this is used."
   1372   (string-join keywords "  "))
   1373 
   1374 (defun denote-format-keywords-for-org-front-matter (keywords)
   1375   "Format front matter KEYWORDS for org file type.
   1376 KEYWORDS is a list of strings.  Consult the `denote-file-types'
   1377 for how this is used."
   1378   (if keywords
   1379       (format ":%s:" (string-join keywords ":"))
   1380     ""))
   1381 
   1382 (defun denote-extract-keywords-from-front-matter (keywords-string)
   1383   "Extract keywords list from front matter KEYWORDS-STRING.
   1384 Split KEYWORDS-STRING into a list of strings.
   1385 
   1386 Consult the `denote-file-types' for how this is used."
   1387   (split-string keywords-string "[:,\s]+" t "[][ \"']+"))
   1388 
   1389 (defvar denote-file-types
   1390   '((org
   1391      :extension ".org"
   1392      :date-function denote-date-org-timestamp
   1393      :front-matter denote-org-front-matter
   1394      :title-key-regexp "^#\\+title\\s-*:"
   1395      :title-value-function denote-format-string-for-org-front-matter
   1396      :title-value-reverse-function denote-trim-whitespace
   1397      :keywords-key-regexp "^#\\+filetags\\s-*:"
   1398      :keywords-value-function denote-format-keywords-for-org-front-matter
   1399      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
   1400      :link denote-org-link-format
   1401      :link-in-context-regexp denote-org-link-in-context-regexp)
   1402     (markdown-yaml
   1403      :extension ".md"
   1404      :date-function denote-date-rfc3339
   1405      :front-matter denote-yaml-front-matter
   1406      :title-key-regexp "^title\\s-*:"
   1407      :title-value-function denote-format-string-for-md-front-matter
   1408      :title-value-reverse-function denote-trim-whitespace-then-quotes
   1409      :keywords-key-regexp "^tags\\s-*:"
   1410      :keywords-value-function denote-format-keywords-for-md-front-matter
   1411      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
   1412      :link denote-md-link-format
   1413      :link-in-context-regexp denote-md-link-in-context-regexp)
   1414     (markdown-toml
   1415      :extension ".md"
   1416      :date-function denote-date-rfc3339
   1417      :front-matter denote-toml-front-matter
   1418      :title-key-regexp "^title\\s-*="
   1419      :title-value-function denote-format-string-for-md-front-matter
   1420      :title-value-reverse-function denote-trim-whitespace-then-quotes
   1421      :keywords-key-regexp "^tags\\s-*="
   1422      :keywords-value-function denote-format-keywords-for-md-front-matter
   1423      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
   1424      :link denote-md-link-format
   1425      :link-in-context-regexp denote-md-link-in-context-regexp)
   1426     (text
   1427      :extension ".txt"
   1428      :date-function denote-date-iso-8601
   1429      :front-matter denote-text-front-matter
   1430      :title-key-regexp "^title\\s-*:"
   1431      :title-value-function denote-format-string-for-org-front-matter
   1432      :title-value-reverse-function denote-trim-whitespace
   1433      :keywords-key-regexp "^tags\\s-*:"
   1434      :keywords-value-function denote-format-keywords-for-text-front-matter
   1435      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
   1436      :link denote-org-link-format
   1437      :link-in-context-regexp denote-org-link-in-context-regexp))
   1438   "Alist of `denote-file-type' and their format properties.
   1439 
   1440 Each element is of the form (SYMBOL PROPERTY-LIST).  SYMBOL is
   1441 one of those specified in `denote-file-type' or an arbitrary
   1442 symbol that defines a new file type.
   1443 
   1444 PROPERTY-LIST is a plist that consists of the following elements:
   1445 
   1446 - `:extension' is a string with the file extension including the
   1447   period.
   1448 
   1449 - `:date-function' is a function that can format a date.  See the
   1450   functions `denote-date-iso-8601', `denote-date-rfc3339', and
   1451   `denote-date-org-timestamp'.
   1452 
   1453 - `:front-matter' is either a string passed to `format' or a
   1454   variable holding such a string.  The `format' function accepts
   1455   four arguments, which come from `denote' in this order: TITLE,
   1456   DATE, KEYWORDS, IDENTIFIER.  Read the doc string of `format' on
   1457   how to reorder arguments.
   1458 
   1459 - `:title-key-regexp' is a regular expression that is used to
   1460   retrieve the title line in a file.  The first line matching
   1461   this regexp is considered the title line.
   1462 
   1463 - `:title-value-function' is the function used to format the raw
   1464   title string for inclusion in the front matter (e.g. to
   1465   surround it with quotes).  Use the `identity' function if no
   1466   further processing is required.
   1467 
   1468 - `:title-value-reverse-function' is the function used to
   1469   retrieve the raw title string from the front matter.  It
   1470   performs the reverse of `:title-value-function'.
   1471 
   1472 - `:keywords-key-regexp' is a regular expression used to retrieve
   1473   the keywords' line in the file.  The first line matching this
   1474   regexp is considered the keywords' line.
   1475 
   1476 - `:keywords-value-function' is the function used to format the
   1477   keywords' list of strings as a single string, with appropriate
   1478   delimiters, for inclusion in the front matter.
   1479 
   1480 - `:keywords-value-reverse-function' is the function used to
   1481   retrieve the keywords' value from the front matter.  It
   1482   performs the reverse of the `:keywords-value-function'.
   1483 
   1484 - `:link' is a string, or variable holding a string, that
   1485   specifies the format of a link.  See the variables
   1486   `denote-org-link-format', `denote-md-link-format'.
   1487 
   1488 - `:link-in-context-regexp' is a regular expression that is used
   1489   to match the aforementioned link format.  See the variables
   1490   `denote-org-link-in-context-regexp',`denote-md-link-in-context-regexp'.
   1491 
   1492 If `denote-file-type' is nil, use the first element of this list
   1493 for new note creation.  The default is `org'.")
   1494 
   1495 (defun denote--date-format-function (file-type)
   1496   "Return date format function of FILE-TYPE."
   1497   (plist-get
   1498    (alist-get file-type denote-file-types)
   1499    :date-function))
   1500 
   1501 (defun denote--file-extension (file-type)
   1502   "Return file type extension based on FILE-TYPE."
   1503   (plist-get
   1504    (alist-get file-type denote-file-types)
   1505    :extension))
   1506 
   1507 (defun denote--front-matter (file-type)
   1508   "Return front matter based on FILE-TYPE."
   1509   (let ((prop (plist-get
   1510                (alist-get file-type denote-file-types)
   1511                :front-matter)))
   1512     (if (symbolp prop)
   1513         (symbol-value prop)
   1514       prop)))
   1515 
   1516 (defun denote--title-key-regexp (file-type)
   1517   "Return the title key regexp associated to FILE-TYPE."
   1518   (plist-get
   1519    (alist-get file-type denote-file-types)
   1520    :title-key-regexp))
   1521 
   1522 (defun denote--title-value-function (file-type)
   1523   "Convert title string to a front matter title, per FILE-TYPE."
   1524   (plist-get
   1525    (alist-get file-type denote-file-types)
   1526    :title-value-function))
   1527 
   1528 (defun denote--title-value-reverse-function (file-type)
   1529   "Convert front matter title to the title string, per FILE-TYPE."
   1530   (plist-get
   1531    (alist-get file-type denote-file-types)
   1532    :title-value-reverse-function))
   1533 
   1534 (defun denote--keywords-key-regexp (file-type)
   1535   "Return the keywords key regexp associated to FILE-TYPE."
   1536   (plist-get
   1537    (alist-get file-type denote-file-types)
   1538    :keywords-key-regexp))
   1539 
   1540 (defun denote--keywords-value-function (file-type)
   1541   "Convert keywords' string to front matter keywords, per FILE-TYPE."
   1542   (plist-get
   1543    (alist-get file-type denote-file-types)
   1544    :keywords-value-function))
   1545 
   1546 (defun denote--keywords-value-reverse-function (file-type)
   1547   "Convert front matter keywords to keywords' list, per FILE-TYPE."
   1548   (plist-get
   1549    (alist-get file-type denote-file-types)
   1550    :keywords-value-reverse-function))
   1551 
   1552 (defun denote--link-format (file-type)
   1553   "Return link format extension based on FILE-TYPE."
   1554   (let ((prop (plist-get
   1555                (alist-get file-type denote-file-types)
   1556                :link)))
   1557     (if (symbolp prop)
   1558         (symbol-value prop)
   1559       prop)))
   1560 
   1561 (defun denote--link-in-context-regexp (file-type)
   1562   "Return link regexp in context based on FILE-TYPE."
   1563   (let ((prop (plist-get
   1564                (alist-get file-type denote-file-types)
   1565                :link-in-context-regexp)))
   1566     (if (symbolp prop)
   1567         (symbol-value prop)
   1568       prop)))
   1569 
   1570 (define-obsolete-function-alias
   1571   'denote--extensions
   1572   'denote-file-type-extensions
   1573   "2.0.0")
   1574 
   1575 (defun denote-file-type-extensions ()
   1576   "Return all file type extensions in `denote-file-types'."
   1577   (delete-dups
   1578    (mapcar (lambda (type)
   1579              (plist-get (cdr type) :extension))
   1580            denote-file-types)))
   1581 
   1582 (defun denote--file-type-keys ()
   1583   "Return all `denote-file-types' keys."
   1584   (delete-dups (mapcar #'car denote-file-types)))
   1585 
   1586 (defun denote--format-front-matter (title date keywords id filetype)
   1587   "Front matter for new notes.
   1588 
   1589 TITLE, DATE, and ID are all strings or functions that return a
   1590 string.  KEYWORDS is a list of strings.  FILETYPE is one of the
   1591 values of `denote-file-type'."
   1592   (let* ((fm (denote--front-matter filetype))
   1593          (title (denote--format-front-matter-title title filetype))
   1594          (kws (denote--format-front-matter-keywords keywords filetype)))
   1595     (if fm (format fm title date kws id) "")))
   1596 
   1597 (defun denote--get-title-line-from-front-matter (title file-type)
   1598   "Retrieve title line from front matter based on FILE-TYPE.
   1599 Format TITLE in the title line.  The returned line does not
   1600 contain the newline."
   1601   (let ((front-matter (denote--format-front-matter title "" nil "" file-type))
   1602         (key-regexp (denote--title-key-regexp file-type)))
   1603     (with-temp-buffer
   1604       (insert front-matter)
   1605       (goto-char (point-min))
   1606       (when (re-search-forward key-regexp nil t 1)
   1607         (buffer-substring-no-properties (line-beginning-position) (line-end-position))))))
   1608 
   1609 (defun denote--get-keywords-line-from-front-matter (keywords file-type)
   1610   "Retrieve keywords line from front matter based on FILE-TYPE.
   1611 Format KEYWORDS in the keywords line.  The returned line does not
   1612 contain the newline."
   1613   (let ((front-matter (denote--format-front-matter "" "" keywords "" file-type))
   1614         (key-regexp (denote--keywords-key-regexp file-type)))
   1615     (with-temp-buffer
   1616       (insert front-matter)
   1617       (goto-char (point-min))
   1618       (when (re-search-forward key-regexp nil t 1)
   1619         (buffer-substring-no-properties (line-beginning-position) (line-end-position))))))
   1620 
   1621 ;;;; Front matter or content retrieval functions
   1622 
   1623 (defun denote-retrieve-filename-identifier (file)
   1624   "Extract identifier from FILE name, if present, else return nil.
   1625 
   1626 To create a new one, refer to the function
   1627 `denote-create-unique-file-identifier'."
   1628   (let ((filename (file-name-nondirectory file)))
   1629     (if (string-match (concat "\\`" denote-id-regexp) filename)
   1630         (match-string-no-properties 0 filename))))
   1631 
   1632 ;; TODO 2023-12-08: Maybe we can only use
   1633 ;; `denote-retrieve-filename-identifier' and remove this function.
   1634 (defun denote-retrieve-filename-identifier-with-error (file)
   1635   "Extract identifier from FILE name, if present, else signal an error."
   1636   (or (denote-retrieve-filename-identifier file)
   1637       (error "Cannot find `%s' as a file with a Denote identifier" file)))
   1638 
   1639 (defun denote-get-identifier (&optional date)
   1640   "Convert DATE into a Denote identifier using `denote-id-format'.
   1641 DATE is parsed by `denote-valid-date-p'.  If DATE is nil, use the
   1642 current time."
   1643   (format-time-string
   1644    denote-id-format
   1645    (when date (denote-valid-date-p date))))
   1646 
   1647 (defun denote-create-unique-file-identifier (file used-ids &optional date)
   1648   "Generate a unique identifier for FILE not in USED-IDS hash-table.
   1649 
   1650 The conditions are as follows:
   1651 
   1652 - If optional DATE is non-nil pass it to `denote-get-identifier'.
   1653   DATE will have to conform with `denote-valid-date-p'.  If it
   1654   does not, return an error.
   1655 
   1656 - If optional DATE is nil, use the file attributes to determine
   1657   the last modified date and format it as an identifier.
   1658 
   1659 - As a fallback, derive an identifier from the current time.
   1660 
   1661 To only return an existing identifier, refer to the function
   1662 `denote-retrieve-filename-identifier'."
   1663   (let ((id (cond
   1664               (date (denote-get-identifier date))
   1665               ((denote--file-attributes-time file))
   1666               (t (denote-get-identifier)))))
   1667     (denote--find-first-unused-id id used-ids)))
   1668 
   1669 (define-obsolete-function-alias
   1670   'denote-retrieve-or-create-file-identifier
   1671   'denote-retrieve-filename-identifier
   1672   "2.1.0")
   1673 
   1674 (defun denote-retrieve-filename-keywords (file)
   1675   "Extract keywords from FILE name, if present, else return nil.
   1676 Return matched keywords as a single string."
   1677   (let ((filename (file-name-nondirectory file)))
   1678     (when (string-match denote-keywords-regexp filename)
   1679       (match-string 1 filename))))
   1680 
   1681 (defun denote-retrieve-filename-signature (file)
   1682   "Extract signature from FILE name, if present, else return nil."
   1683   (let ((filename (file-name-nondirectory file)))
   1684     (when (string-match denote-signature-regexp filename)
   1685       (match-string 1 filename))))
   1686 
   1687 (defun denote-retrieve-filename-title (file)
   1688   "Extract Denote title component from FILE name, else return nil."
   1689   (let ((filename (file-name-nondirectory file)))
   1690     (when (string-match denote-title-regexp filename)
   1691       (match-string 1 filename))))
   1692 
   1693 (defun denote--file-with-temp-buffer-subr (file)
   1694   "Return path to FILE or its buffer together with the appropriate function.
   1695 Subroutine of `denote--file-with-temp-buffer'."
   1696   (let* ((buffer (get-file-buffer file))
   1697          (file-exists (file-exists-p file))
   1698          (buffer-modified (buffer-modified-p buffer)))
   1699     (cond
   1700      ((or (and file-exists
   1701                buffer
   1702                (not buffer-modified)
   1703                (not (eq buffer-modified 'autosaved)))
   1704           (and file-exists (not buffer)))
   1705       (cons #'insert-file-contents file))
   1706      (buffer
   1707       (cons #'insert-buffer buffer))
   1708      ;; (t
   1709      ;;  (error "Cannot find anything about file `%s'" file))
   1710      )))
   1711 
   1712 (defmacro denote--file-with-temp-buffer (file &rest body)
   1713   "If FILE exists, insert its contents in a temp buffer and call BODY."
   1714   (declare (indent 1))
   1715   `(when-let ((file-and-function (denote--file-with-temp-buffer-subr ,file)))
   1716      (with-temp-buffer
   1717        (funcall (car file-and-function) (cdr file-and-function))
   1718        (goto-char (point-min))
   1719        ,@body)))
   1720 
   1721 (defun denote-retrieve-front-matter-title-value (file file-type)
   1722   "Return title value from FILE front matter per FILE-TYPE."
   1723   (denote--file-with-temp-buffer file
   1724     (when (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   1725       (funcall (denote--title-value-reverse-function file-type)
   1726                (buffer-substring-no-properties (point) (line-end-position))))))
   1727 
   1728 (defun denote-retrieve-front-matter-title-line (file file-type)
   1729   "Return title line from FILE front matter per FILE-TYPE."
   1730   (denote--file-with-temp-buffer file
   1731     (when (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   1732       (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
   1733 
   1734 (defun denote-retrieve-front-matter-keywords-value (file file-type)
   1735   "Return keywords value from FILE front matter per FILE-TYPE.
   1736 The return value is a list of strings.  To get a combined string
   1737 the way it would appear in a Denote file name, use
   1738 `denote-retrieve-front-matter-keywords-value-as-string'."
   1739   (denote--file-with-temp-buffer file
   1740     (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1741       (funcall (denote--keywords-value-reverse-function file-type)
   1742                (buffer-substring-no-properties (point) (line-end-position))))))
   1743 
   1744 (defun denote-retrieve-front-matter-keywords-value-as-string (file file-type)
   1745   "Return keywords value from FILE front matter per FILE-TYPE.
   1746 The return value is a string, with the underscrore as a separator
   1747 between individual keywords.  To get a list of strings instead,
   1748 use `denote-retrieve-front-matter-keywords-value' (the current function uses
   1749 that internally)."
   1750   (denote-keywords-combine (denote-retrieve-front-matter-keywords-value file file-type)))
   1751 
   1752 (defun denote-retrieve-front-matter-keywords-line (file file-type)
   1753   "Return keywords line from FILE front matter per FILE-TYPE."
   1754   (denote--file-with-temp-buffer file
   1755     (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1756       (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
   1757 
   1758 (defalias 'denote-retrieve-title-value 'denote-retrieve-front-matter-title-value
   1759  "Alias for `denote-retrieve-front-matter-title-value'.")
   1760 
   1761 (defalias 'denote-retrieve-title-line 'denote-retrieve-front-matter-title-line
   1762  "Alias for `denote-retrieve-front-matter-title-line'.")
   1763 
   1764 (defalias 'denote-retrieve-keywords-value 'denote-retrieve-front-matter-keywords-value
   1765  "Alias for `denote-retrieve-front-matter-keywords-value'.")
   1766 
   1767 (defalias 'denote-retrieve-keywords-line 'denote-retrieve-front-matter-keywords-line
   1768  "Alias for `denote-retrieve-front-matter-keywords-line'.")
   1769 
   1770 (defalias 'denote-retrieve-keywords-value-as-string 'denote-retrieve-front-matter-keywords-value-as-string
   1771  "Alias for `denote-retrieve-front-matter-keywords-value-as-string'.")
   1772 
   1773 (define-obsolete-function-alias
   1774   'denote--retrieve-title-or-filename
   1775   'denote-retrieve-title-or-filename
   1776   "2.3.0")
   1777 
   1778 (defun denote-retrieve-title-or-filename (file type)
   1779   "Return appropriate title for FILE given its TYPE.
   1780 Try to find the value of the title in the front matter of FILE,
   1781 otherwise use its file name.
   1782 
   1783 This is a wrapper for `denote-retrieve-front-matter-title-value' and
   1784 `denote-retrieve-filename-title'."
   1785   (if-let (((denote-file-is-note-p file))
   1786            (title (denote-retrieve-front-matter-title-value file type))
   1787            ((not (string-blank-p title))))
   1788       title
   1789     (or (denote-retrieve-filename-title file)
   1790         (file-name-base file))))
   1791 
   1792 (defun denote--retrieve-location-in-xrefs (identifier)
   1793   "Return list of xrefs for IDENTIFIER with their respective location.
   1794 Limit the search to text files, per `denote-directory-files' with
   1795 non-nil `text-only' parameter."
   1796   (mapcar #'xref-match-item-location
   1797           (xref-matches-in-files identifier
   1798                                  (denote-directory-files nil nil :text-only))))
   1799 
   1800 (defun denote--retrieve-group-in-xrefs (identifier)
   1801   "Access location of xrefs for IDENTIFIER and group them per file.
   1802 See `denote--retrieve-locations-in-xrefs'."
   1803   (mapcar #'xref-location-group
   1804           (denote--retrieve-location-in-xrefs identifier)))
   1805 
   1806 (defun denote--retrieve-files-in-xrefs (identifier)
   1807   "Return sorted, deduplicated file names with IDENTIFIER in their contents."
   1808   (sort
   1809    (delete-dups
   1810     (denote--retrieve-group-in-xrefs identifier))
   1811    #'string-collate-lessp))
   1812 
   1813 ;;;; New note
   1814 
   1815 ;;;;; Common helpers for new notes
   1816 
   1817 (defun denote-format-file-name (dir-path id keywords title extension signature)
   1818   "Format file name.
   1819 DIR-PATH, ID, KEYWORDS, TITLE, EXTENSION and SIGNATURE are
   1820 expected to be supplied by `denote' or equivalent command.
   1821 
   1822 DIR-PATH is a string pointing to a directory.  It ends with a
   1823 forward slash (the function `denote-directory' makes sure this is
   1824 the case when returning the value of the variable `denote-directory').
   1825 DIR-PATH cannot be nil or an empty string.
   1826 
   1827 ID is a string holding the identifier of the note.  It cannot be
   1828 nil or an empty string and must match `denote-id-regexp'.
   1829 
   1830 DIR-PATH and ID form the base file name.
   1831 
   1832 KEYWORDS is a list of strings that is reduced to a single string
   1833 by `denote-keywords-combine'.  KEYWORDS can be an empty list or
   1834 a nil value, in which case the relevant file name component is
   1835 not added to the base file name.
   1836 
   1837 TITLE and SIGNATURE are strings.  They can be an empty string, in
   1838 which case their respective file name component is not added to
   1839 the base file name.
   1840 
   1841 EXTENSION is a string that contains a dot followed by the file
   1842 type extension.  It can be an empty string or a nil value, in
   1843 which case it is not added to the base file name."
   1844   (cond
   1845    ((null dir-path)
   1846     (error "DIR-PATH must not be nil"))
   1847    ((string-empty-p dir-path)
   1848     (error "DIR-PATH must not be an empty string"))
   1849    ((not (string-suffix-p "/" dir-path))
   1850     (error "DIR-PATH does not end with a / as directories ought to"))
   1851    ((null id)
   1852     (error "ID must not be nil"))
   1853    ((string-empty-p id)
   1854     (error "ID must not be an empty string"))
   1855    ((not (string-match-p denote-id-regexp id))
   1856     (error "ID `%s' does not match `denote-id-regexp'" id)))
   1857   (let ((file-name (concat dir-path id)))
   1858     (when (and signature (not (string-empty-p signature)))
   1859       (setq file-name (concat file-name "==" (denote-sluggify 'signature signature))))
   1860     (when (and title (not (string-empty-p title)))
   1861       (setq file-name (concat file-name "--" (denote-sluggify 'title title))))
   1862     (when keywords
   1863       (setq file-name (concat file-name "__" (denote-keywords-combine (denote-sluggify-keywords keywords)))))
   1864     (concat file-name extension)))
   1865 
   1866 (defun denote--format-front-matter-title (title file-type)
   1867   "Format TITLE according to FILE-TYPE for the file's front matter."
   1868   (funcall (denote--title-value-function file-type) title))
   1869 
   1870 (defun denote--format-front-matter-keywords (keywords file-type)
   1871   "Format KEYWORDS according to FILE-TYPE for the file's front matter.
   1872 Apply `denote-sluggify' to KEYWORDS."
   1873   (let ((kws (denote-sluggify-keywords keywords)))
   1874     (funcall (denote--keywords-value-function file-type) kws)))
   1875 
   1876 (defun denote--path (title keywords dir id file-type signature)
   1877   "Return path to new file.
   1878 Use ID, TITLE, KEYWORDS, FILE-TYPE and SIGNATURE to construct
   1879 path to DIR."
   1880   (denote-format-file-name
   1881    dir id keywords title (denote--file-extension file-type) signature))
   1882 
   1883 ;; Adapted from `org-hugo--org-date-time-to-rfc3339' in the `ox-hugo'
   1884 ;; package: <https://github.com/kaushalmodi/ox-hugo>.
   1885 (defun denote-date-rfc3339 (date)
   1886   "Format DATE using the RFC3339 specification."
   1887   (replace-regexp-in-string
   1888    "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2"
   1889    (format-time-string "%FT%T%z" date)))
   1890 
   1891 (defun denote-date-org-timestamp (date)
   1892   "Format DATE using the Org inactive timestamp notation."
   1893   (format-time-string "[%F %a %R]" date))
   1894 
   1895 (defun denote-date-iso-8601 (date)
   1896   "Format DATE according to ISO 8601 standard."
   1897   (format-time-string "%F" date))
   1898 
   1899 (defun denote--date (date file-type)
   1900   "Expand DATE in an appropriate format for FILE-TYPE."
   1901   (let ((format denote-date-format))
   1902     (cond
   1903      (format
   1904       (format-time-string format date))
   1905      ((when-let ((fn (denote--date-format-function file-type)))
   1906         (funcall fn date)))
   1907      (t
   1908       (denote-date-org-timestamp date)))))
   1909 
   1910 (defun denote--prepare-note (title keywords date id directory file-type template signature)
   1911   "Prepare a new note file.
   1912 
   1913 Arguments TITLE, KEYWORDS, DATE, ID, DIRECTORY, FILE-TYPE,
   1914 TEMPLATE, and SIGNATURE should be valid for note creation."
   1915   (let* ((path (denote--path title keywords directory id file-type signature))
   1916          (buffer (find-file path))
   1917          (header (denote--format-front-matter
   1918                   title (denote--date date file-type) keywords
   1919                   id
   1920                   file-type)))
   1921     (with-current-buffer buffer
   1922       (insert header)
   1923       (insert template))))
   1924 
   1925 (defun denote--dir-in-denote-directory-p (directory)
   1926   "Return non-nil if DIRECTORY is in variable `denote-directory'."
   1927   (and directory
   1928        (string-prefix-p (denote-directory)
   1929                         (expand-file-name directory))))
   1930 
   1931 (defun denote--valid-file-type (filetype)
   1932   "Return a valid filetype symbol given the argument FILETYPE.
   1933 If none is found, the first element of `denote-file-types' is
   1934 returned."
   1935   (let ((type (cond
   1936                ((stringp filetype) (intern filetype))
   1937                ((symbolp filetype) filetype)
   1938                (t (error "The `%s' is neither a string nor a symbol" filetype)))))
   1939     (if (memq type (denote--file-type-keys))
   1940         type
   1941       (caar denote-file-types))))
   1942 
   1943 (defun denote--date-add-current-time (date)
   1944   "Add current time to DATE, if necessary.
   1945 The idea is to turn 2020-01-15 into 2020-01-15 16:19 so that the
   1946 hour and minute component is not left to 00:00.
   1947 
   1948 This reduces the burden on the user who would otherwise need to
   1949 input that value in order to avoid the error of duplicate
   1950 identifiers.
   1951 
   1952 It also addresses a difference between Emacs 28 and Emacs 29
   1953 where the former does not read dates without a time component."
   1954   (if (<= (length date) 10)
   1955       (format "%s %s" date (format-time-string "%H:%M:%S" (current-time)))
   1956     date))
   1957 
   1958 (define-obsolete-function-alias
   1959   'denote--valid-date
   1960   'denote-valid-date-p
   1961   "2.3.0")
   1962 
   1963 (defun denote-valid-date-p (date)
   1964   "Return DATE as a valid date.
   1965 A valid DATE is a value that can be parsed by either
   1966 `decode-time' or `date-to-time'.  Those functions signal an error
   1967 if DATE is a value they do not recognise.
   1968 
   1969 If DATE is nil, return nil."
   1970   (if (and (or (numberp date) (listp date))
   1971            (decode-time date))
   1972       date
   1973     (date-to-time (denote--date-add-current-time date))))
   1974 
   1975 (defun denote-parse-date (date)
   1976   "Return DATE as an appropriate value for the `denote' command.
   1977 Pass DATE through `denote-valid-date-p' and use its return value.
   1978 If either that or DATE is nil, return `current-time'."
   1979   (or (denote-valid-date-p date) (current-time)))
   1980 
   1981 (defun denote--buffer-file-names ()
   1982   "Return file names of Denote buffers."
   1983   (delq nil
   1984         (mapcar
   1985          (lambda (buffer)
   1986            (when-let (((buffer-live-p buffer))
   1987                       (file (buffer-file-name buffer))
   1988                       ((denote-filename-is-note-p file)))
   1989              file))
   1990          (buffer-list))))
   1991 
   1992 (defun denote--id-exists-p (identifier)
   1993   "Return non-nil if IDENTIFIER already exists."
   1994   (seq-some
   1995    (lambda (file)
   1996      (string-prefix-p identifier (file-name-nondirectory file)))
   1997    (append (denote-directory-files) (denote--buffer-file-names))))
   1998 
   1999 (defun denote--get-all-used-ids ()
   2000   "Return a hash-table of all used identifiers.
   2001 It checks files in variable `denote-directory' and active buffer files."
   2002   (let* ((ids (make-hash-table :test 'equal))
   2003          (file-names (mapcar
   2004                       (lambda (file) (file-name-nondirectory file))
   2005                       (denote-directory-files)))
   2006          (names (append file-names (denote--buffer-file-names))))
   2007     (dolist (name names)
   2008       (let ((id (denote-retrieve-filename-identifier name)))
   2009         (puthash id t ids)))
   2010     ids))
   2011 
   2012 (defun denote--find-first-unused-id (id used-ids)
   2013   "Return the first unused id starting at ID from USED-IDS.
   2014 USED-IDS is a hash-table of all used IDs.  If ID is already used,
   2015 increment it 1 second at a time until an available id is found."
   2016   (let ((current-id id))
   2017     (while (gethash current-id used-ids)
   2018       (setq current-id (denote-get-identifier (time-add (date-to-time current-id) 1))))
   2019     current-id))
   2020 
   2021 (make-obsolete 'denote-barf-duplicate-id nil "2.1.0")
   2022 
   2023 (defvar denote-command-prompt-history nil
   2024   "Minibuffer history for `denote-command-prompt'.")
   2025 
   2026 (defalias 'denote--command-prompt-history 'denote-command-prompt-history
   2027   "Compatibility alias for `denote-command-prompt-history'.")
   2028 
   2029 (defun denote-command-prompt ()
   2030   "Prompt for command among `denote-commands-for-new-notes'."
   2031   (let ((default (car denote-command-prompt-history)))
   2032     (intern
   2033      (completing-read
   2034       (format-prompt "Run note-creating Denote command" default)
   2035       denote-commands-for-new-notes nil :require-match
   2036       nil 'denote-command-prompt-history default))))
   2037 
   2038 ;;;;; The `denote' command and its prompts
   2039 
   2040 (defun denote--prompt-with-completion-p (fn)
   2041   "Return non-nil if FN prompt should perform completion.
   2042 FN is one among `denote-prompts-with-history-as-completion' and performs
   2043 completion when the user option `denote-history-completion-in-prompts'
   2044 is non-nil."
   2045   (and denote-history-completion-in-prompts
   2046        (memq fn denote-prompts-with-history-as-completion)))
   2047 
   2048 (defvar denote-ignore-region-in-denote-command nil
   2049   "If non-nil, the region is ignored by the `denote' command.
   2050 
   2051 The `denote' command uses the region as the default title when
   2052 prompted for a title.  When this variable is non-nil, the
   2053 `denote' command ignores the region.  This variable is useful in
   2054 commands that have their own way of handling the region.")
   2055 
   2056 (defvar denote-title-prompt-current-default nil
   2057   "Currently bound default title for `denote-title-prompt'.
   2058 Set the value of this variable within the lexical scope of a
   2059 command that needs to supply a default title before calling
   2060 `denote-title-prompt'.")
   2061 
   2062 (defun denote--command-with-features (command force-use-file-prompt-as-default-title force-ignore-region force-save in-background)
   2063   "Execute file-creating COMMAND with specified features.
   2064 
   2065 COMMAND is the symbol of a file-creating command to call, such as
   2066 `denote' or `denote-signature'.
   2067 
   2068 With non-nil FORCE-USE-FILE-PROMPT-AS-DEFAULT-TITLE, use the last
   2069 item of `denote-file-history' as the default title of the title
   2070 prompt.  This is useful in a command such as `denote-link' where
   2071 the entry of the file prompt can be reused as the default title.
   2072 
   2073 With non-nil FORCE-IGNORE-REGION, the region is ignore when
   2074 creating the note, i.e. it will not be used as the initial title
   2075 in a title prompt.  Else, the value of
   2076 `denote-ignore-region-in-denote-command' is respected.
   2077 
   2078 With non-nil FORCE-SAVE, the file is saved at the end of the note
   2079 creation.  Else, the value of `denote-save-buffer-after-creation'
   2080 is respected.
   2081 
   2082 With non-nil IN-BACKGROUND, the note creation happens in the
   2083 background, i.e. the note's buffer will not be displayed after
   2084 the note is created.
   2085 
   2086 Note that if all parameters except COMMAND are nil, this is
   2087 equivalent to `(call-interactively command)'.
   2088 
   2089 The path of the newly created file is returned."
   2090   (let ((denote-save-buffer-after-creation
   2091          (or force-save denote-save-buffer-after-creation))
   2092         (denote-ignore-region-in-denote-command
   2093          (or force-ignore-region denote-ignore-region-in-denote-command))
   2094         (denote-title-prompt-current-default
   2095          (if force-use-file-prompt-as-default-title
   2096              (when denote-file-history
   2097                (file-name-nondirectory (pop denote-file-history)))
   2098            denote-title-prompt-current-default))
   2099         (path))
   2100     (if in-background
   2101         (save-window-excursion
   2102           (call-interactively command)
   2103           (setq path (buffer-file-name)))
   2104       (call-interactively command)
   2105       (setq path (buffer-file-name)))
   2106     path))
   2107 
   2108 ;;;###autoload
   2109 (defun denote (&optional title keywords file-type subdirectory date template signature)
   2110   "Create a new note with the appropriate metadata and file name.
   2111 
   2112 Run the `denote-after-new-note-hook' after creating the new note.
   2113 
   2114 When called interactively, the metadata and file name are prompted
   2115 according to the value of `denote-prompts'.
   2116 
   2117 When called from Lisp, all arguments are optional.
   2118 
   2119 - TITLE is a string or a function returning a string.
   2120 
   2121 - KEYWORDS is a list of strings.  The list can be empty or the
   2122   value can be set to nil.
   2123 
   2124 - FILE-TYPE is a symbol among those described in `denote-file-type'.
   2125 
   2126 - SUBDIRECTORY is a string representing the path to either the
   2127   value of the variable `denote-directory' or a subdirectory
   2128   thereof.  The subdirectory must exist: Denote will not create
   2129   it.  If SUBDIRECTORY does not resolve to a valid path, the
   2130   variable `denote-directory' is used instead.
   2131 
   2132 - DATE is a string representing a date like 2022-06-30 or a date
   2133   and time like 2022-06-16 14:30.  A nil value or an empty string
   2134   is interpreted as the `current-time'.
   2135 
   2136 - TEMPLATE is a symbol which represents the key of a cons cell in
   2137   the user option `denote-templates'.  The value of that key is
   2138   inserted to the newly created buffer after the front matter.
   2139 
   2140 - SIGNATURE is a string or a function returning a string."
   2141   (interactive
   2142    (let ((args (make-vector 7 nil)))
   2143      (dolist (prompt denote-prompts)
   2144        (pcase prompt
   2145          ('title (aset args 0 (denote-title-prompt
   2146                                (when (and (not denote-ignore-region-in-denote-command)
   2147                                           (use-region-p))
   2148                                  (buffer-substring-no-properties
   2149                                   (region-beginning)
   2150                                   (region-end))))))
   2151          ('keywords (aset args 1 (denote-keywords-prompt)))
   2152          ('file-type (aset args 2 (denote-file-type-prompt)))
   2153          ('subdirectory (aset args 3 (denote-subdirectory-prompt)))
   2154          ('date (aset args 4 (denote-date-prompt)))
   2155          ('template (aset args 5 (denote-template-prompt)))
   2156          ('signature (aset args 6 (denote-signature-prompt)))))
   2157      (append args nil)))
   2158   (let* ((title (or title ""))
   2159          (file-type (denote--valid-file-type (or file-type denote-file-type)))
   2160          (kws (denote-keywords-sort keywords))
   2161          (date (denote-parse-date date))
   2162          (id (denote--find-first-unused-id
   2163               (denote-get-identifier date)
   2164               (denote--get-all-used-ids)))
   2165          (directory (if (denote--dir-in-denote-directory-p subdirectory)
   2166                         (file-name-as-directory subdirectory)
   2167                       (denote-directory)))
   2168          (template (if (stringp template)
   2169                        template
   2170                      (or (alist-get template denote-templates) "")))
   2171          (signature (or signature "")))
   2172     (denote--prepare-note title kws date id directory file-type template signature)
   2173     (when denote-save-buffer-after-creation (save-buffer))
   2174     (denote--keywords-add-to-history keywords)
   2175     (run-hooks 'denote-after-new-note-hook)))
   2176 
   2177 (defvar denote-title-history nil
   2178   "Minibuffer history of `denote-title-prompt'.")
   2179 
   2180 (defalias 'denote--title-history 'denote-title-history
   2181   "Compatibility alias for `denote-title-history'.")
   2182 
   2183 (defmacro denote--with-conditional-completion (fn prompt history &optional initial-value default-value)
   2184   "Produce body of FN that may perform completion.
   2185 Use PROMPT, HISTORY, INITIAL-VALUE, and DEFAULT-VALUE as arguments for
   2186 the given minibuffer prompt."
   2187   `(if (denote--prompt-with-completion-p ,fn)
   2188        ;; NOTE 2023-10-27: By default SPC performs completion in the
   2189        ;; minibuffer.  We do not want that, as the user should be able to
   2190        ;; input an arbitrary string, while still performing completion
   2191        ;; against their input history.
   2192        (minibuffer-with-setup-hook
   2193            (lambda ()
   2194              (use-local-map
   2195               (let ((map (make-composed-keymap nil (current-local-map))))
   2196                 (define-key map (kbd "SPC") nil)
   2197                 map)))
   2198          (completing-read ,prompt ,history nil nil ,initial-value ',history ,default-value))
   2199      (read-string ,prompt ,initial-value ',history ,default-value)))
   2200 
   2201 (defun denote-title-prompt (&optional initial-title prompt-text)
   2202   "Prompt for title string.
   2203 
   2204 With optional INITIAL-TITLE use it as the initial minibuffer
   2205 text.  With optional PROMPT-TEXT use it in the minibuffer instead
   2206 of the default prompt.
   2207 
   2208 Previous inputs at this prompt are available for minibuffer completion
   2209 if the user option `denote-history-completion-in-prompts' is set to a
   2210 non-nil value."
   2211   (denote--with-conditional-completion
   2212    'denote-title-prompt
   2213    (format-prompt (or prompt-text "New file TITLE") denote-title-prompt-current-default)
   2214    denote-title-history
   2215    initial-title
   2216    denote-title-prompt-current-default))
   2217 
   2218 (defvar denote-file-type-history nil
   2219   "Minibuffer history of `denote-file-type-prompt'.")
   2220 
   2221 (defalias 'denote--file-type-history 'denote-file-type-history
   2222   "Compatibility alias for `denote-file-type-history'.")
   2223 
   2224 (defun denote-file-type-prompt ()
   2225   "Prompt for `denote-file-type'.
   2226 Note that a non-nil value other than `text', `markdown-yaml', and
   2227 `markdown-toml' falls back to an Org file type.  We use `org'
   2228 here for clarity."
   2229   (completing-read
   2230    "Select file TYPE: " (denote--file-type-keys) nil t
   2231    nil 'denote-file-type-history))
   2232 
   2233 (defvar denote-date-history nil
   2234   "Minibuffer history of `denote-date-prompt'.")
   2235 
   2236 (defalias 'denote--date-history 'denote-date-history
   2237   "Compatibility alias for `denote-date-history'.")
   2238 
   2239 (declare-function org-read-date "org" (&optional with-time to-time from-string prompt default-time default-input inactive))
   2240 
   2241 (defun denote-date-prompt ()
   2242   "Prompt for date, expecting YYYY-MM-DD or that plus HH:MM.
   2243 Use Org's more advanced date selection utility if the user option
   2244 `denote-date-prompt-use-org-read-date' is non-nil."
   2245   (if (and denote-date-prompt-use-org-read-date
   2246            (require 'org nil :no-error))
   2247       (let* ((time (org-read-date nil t))
   2248              (org-time-seconds (format-time-string "%S" time))
   2249              (cur-time-seconds (format-time-string "%S" (current-time))))
   2250         ;; When the user does not input a time, org-read-date defaults to 00 for seconds.
   2251         ;; When the seconds are 00, we add the current seconds to avoid identifier collisions.
   2252         (when (string-equal "00" org-time-seconds)
   2253           (setq time (time-add time (string-to-number cur-time-seconds))))
   2254         (format-time-string "%Y-%m-%d %H:%M:%S" time))
   2255     (read-string
   2256      "DATE and TIME for note (e.g. 2022-06-16 14:30): "
   2257      nil 'denote-date-history)))
   2258 
   2259 (defun denote-prompt-for-date-return-id ()
   2260   "Use `denote-date-prompt' and return it as `denote-id-format'."
   2261   (denote-get-identifier (denote-date-prompt)))
   2262 
   2263 (defvar denote-subdirectory-history nil
   2264   "Minibuffer history of `denote-subdirectory-prompt'.")
   2265 
   2266 (defalias 'denote--subdir-history 'denote-subdirectory-history
   2267   "Compatibility alias for `denote-subdirectory-history'.")
   2268 
   2269 ;; Making it a completion table is useful for packages that read the
   2270 ;; metadata, such as `marginalia' and `embark'.
   2271 (defun denote--subdirs-completion-table (dirs)
   2272   "Match DIRS as a completion table."
   2273   (let* ((def (car denote-subdirectory-history))
   2274          (table (denote--completion-table 'file dirs))
   2275          (prompt (if def
   2276                      (format "Select SUBDIRECTORY [%s]: " def)
   2277                    "Select SUBDIRECTORY: ")))
   2278     (completing-read prompt table nil t nil 'denote-subdirectory-history def)))
   2279 
   2280 (defun denote-subdirectory-prompt ()
   2281   "Prompt for subdirectory of the variable `denote-directory'.
   2282 The table uses the `file' completion category (so it works with
   2283 packages such as `marginalia' and `embark')."
   2284   (let* ((root (directory-file-name (denote-directory)))
   2285          (subdirs (denote-directory-subdirectories))
   2286          (dirs (push root subdirs)))
   2287     (denote--subdirs-completion-table dirs)))
   2288 
   2289 (defvar denote-template-history nil
   2290   "Minibuffer history of `denote-template-prompt'.")
   2291 
   2292 (defalias 'denote--template-history 'denote-template-history
   2293   "Compatibility alias for `denote-template-history'.")
   2294 
   2295 (defun denote-template-prompt ()
   2296   "Prompt for template key in `denote-templates' and return its value."
   2297   (let ((templates denote-templates))
   2298     (alist-get
   2299      (intern
   2300       (completing-read
   2301        "Select TEMPLATE key: " (mapcar #'car templates)
   2302        nil t nil 'denote-template-history))
   2303      templates)))
   2304 
   2305 (defvar denote-signature-history nil
   2306   "Minibuffer history of `denote-signature-prompt'.")
   2307 
   2308 (defalias 'denote--signature-history 'denote-signature-history
   2309   "Compatibility alias for `denote-signature-history'.")
   2310 
   2311 (defun denote-signature-prompt (&optional initial-signature prompt-text)
   2312   "Prompt for signature string.
   2313 With optional INITIAL-SIGNATURE use it as the initial minibuffer
   2314 text.  With optional PROMPT-TEXT use it in the minibuffer instead
   2315 of the default prompt.
   2316 
   2317 Previous inputs at this prompt are available for minibuffer completion
   2318 if the user option `denote-history-completion-in-prompts' is set to a
   2319 non-nil value."
   2320   (when (and initial-signature (string-empty-p initial-signature))
   2321     (setq initial-signature nil))
   2322   (denote--with-conditional-completion
   2323    'denote-signature-prompt
   2324    (format-prompt (or prompt-text "New file SIGNATURE") nil)
   2325    denote-signature-history
   2326    initial-signature))
   2327 
   2328 (defvar denote-files-matching-regexp-history nil
   2329   "Minibuffer history of `denote-files-matching-regexp-prompt'.")
   2330 
   2331 (defalias 'denote--files-matching-regexp-hist 'denote-files-matching-regexp-history
   2332   "Compatibility alias for `denote-files-matching-regexp-history'.")
   2333 
   2334 (defun denote-files-matching-regexp-prompt (&optional prompt-text)
   2335   "Prompt for REGEXP to filter Denote files by.
   2336 With optional PROMPT-TEXT use it instead of a generic prompt."
   2337   (denote--with-conditional-completion
   2338    'denote-files-matching-regexp-prompt
   2339    (format-prompt (or prompt-text "Match files with the given REGEXP") nil)
   2340    denote-files-matching-regexp-history))
   2341 
   2342 ;;;;; Convenience commands as `denote' variants
   2343 
   2344 (defalias 'denote-create-note 'denote
   2345   "Alias for `denote' command.")
   2346 
   2347 (defun denote--add-prompts (additional-prompts)
   2348   "Add all the elements in the ADDITIONAL-PROMPTS list to `denote-prompts'."
   2349   (seq-union additional-prompts denote-prompts))
   2350 
   2351 ;;;###autoload
   2352 (defun denote-type ()
   2353   "Create note while prompting for a file type.
   2354 
   2355 This is the equivalent of calling `denote' when `denote-prompts'
   2356 has the `file-type' prompt appended to its existing prompts."
   2357   (declare (interactive-only t))
   2358   (interactive)
   2359   (let ((denote-prompts (denote--add-prompts '(file-type))))
   2360     (call-interactively #'denote)))
   2361 
   2362 (defalias 'denote-create-note-using-type 'denote-type
   2363   "Alias for `denote-type' command.")
   2364 
   2365 ;;;###autoload
   2366 (defun denote-date ()
   2367   "Create note while prompting for a date.
   2368 
   2369 The date can be in YEAR-MONTH-DAY notation like 2022-06-30 or
   2370 that plus the time: 2022-06-16 14:30.  When the user option
   2371 `denote-date-prompt-use-org-read-date' is non-nil, the date
   2372 prompt uses the more powerful Org+calendar system.
   2373 
   2374 This is the equivalent of calling `denote' when `denote-prompts'
   2375 has the `date' prompt appended to its existing prompts."
   2376   (declare (interactive-only t))
   2377   (interactive)
   2378   (let ((denote-prompts (denote--add-prompts '(date))))
   2379     (call-interactively #'denote)))
   2380 
   2381 (defalias 'denote-create-note-using-date 'denote-date
   2382   "Alias for `denote-date' command.")
   2383 
   2384 ;;;###autoload
   2385 (defun denote-subdirectory ()
   2386   "Create note while prompting for a subdirectory.
   2387 
   2388 Available candidates include the value of the variable
   2389 `denote-directory' and any subdirectory thereof.
   2390 
   2391 This is the equivalent of calling `denote' when `denote-prompts'
   2392 has the `subdirectory' prompt appended to its existing prompts."
   2393   (declare (interactive-only t))
   2394   (interactive)
   2395   (let ((denote-prompts (denote--add-prompts '(subdirectory))))
   2396     (call-interactively #'denote)))
   2397 
   2398 (defalias 'denote-create-note-in-subdirectory 'denote-subdirectory
   2399   "Alias for `denote-subdirectory' command.")
   2400 
   2401 ;;;###autoload
   2402 (defun denote-template ()
   2403   "Create note while prompting for a template.
   2404 
   2405 Available candidates include the keys in the `denote-templates'
   2406 alist.  The value of the selected key is inserted in the newly
   2407 created note after the front matter.
   2408 
   2409 This is the equivalent of calling `denote' when `denote-prompts'
   2410 has the `template' prompt appended to its existing prompts."
   2411   (declare (interactive-only t))
   2412   (interactive)
   2413   (let ((denote-prompts (denote--add-prompts '(template))))
   2414     (call-interactively #'denote)))
   2415 
   2416 (defalias 'denote-create-note-with-template 'denote-template
   2417   "Alias for `denote-template' command.")
   2418 
   2419 ;;;###autoload
   2420 (defun denote-signature ()
   2421   "Create note while prompting for a file signature.
   2422 
   2423 This is the equivalent of calling `denote' when `denote-prompts'
   2424 has the `signature' prompt appended to its existing prompts."
   2425   (declare (interactive-only t))
   2426   (interactive)
   2427   (let ((denote-prompts (denote--add-prompts '(signature))))
   2428     (call-interactively #'denote)))
   2429 
   2430 (defalias 'denote-create-note-using-signature 'denote-signature
   2431   "Alias for `denote-signature' command.")
   2432 
   2433 ;;;###autoload
   2434 (defun denote-region ()
   2435   "Call `denote' and insert therein the text of the active region."
   2436   (declare (interactive-only t))
   2437   (interactive)
   2438   (if-let (((region-active-p))
   2439            ;; We capture the text early, otherwise it will be empty
   2440            ;; the moment `insert' is called.
   2441            (text (buffer-substring-no-properties (region-beginning) (region-end))))
   2442       (progn
   2443         (let ((denote-ignore-region-in-denote-command t))
   2444           (call-interactively 'denote))
   2445         (push-mark (point))
   2446         (insert text)
   2447         (run-hook-with-args 'denote-region-after-new-note-functions (mark) (point)))
   2448     (call-interactively 'denote)))
   2449 
   2450 ;;;;; Other convenience commands
   2451 
   2452 ;;;###autoload
   2453 (defun denote-open-or-create (target)
   2454   "Visit TARGET file in variable `denote-directory'.
   2455 If file does not exist, invoke `denote' to create a file.
   2456 
   2457 If TARGET file does not exist, add the user input that was used
   2458 to search for it to the minibuffer history of the
   2459 `denote-file-prompt'.  The user can then retrieve and possibly
   2460 further edit their last input, using it as the newly created
   2461 note's actual title.  At the `denote-file-prompt' type
   2462 \\<minibuffer-local-map>\\[previous-history-element]."
   2463   (interactive (list (denote-file-prompt)))
   2464   (if (and target (file-exists-p target))
   2465       (find-file target)
   2466     (denote--command-with-features #'denote :use-file-prompt-as-def-title nil nil nil)))
   2467 
   2468 ;;;###autoload
   2469 (defun denote-open-or-create-with-command ()
   2470   "Visit TARGET file in variable `denote-directory'.
   2471 If file does not exist, invoke `denote' to create a file.
   2472 
   2473 If TARGET file does not exist, add the user input that was used
   2474 to search for it to the minibuffer history of the
   2475 `denote-file-prompt'.  The user can then retrieve and possibly
   2476 further edit their last input, using it as the newly created
   2477 note's actual title.  At the `denote-file-prompt' type
   2478 \\<minibuffer-local-map>\\[previous-history-element]."
   2479   (declare (interactive-only t))
   2480   (interactive)
   2481   (let ((target (denote-file-prompt)))
   2482     (if (and target (file-exists-p target))
   2483         (find-file target)
   2484       (denote--command-with-features (denote-command-prompt) :use-file-prompt-as-def-title nil nil nil))))
   2485 
   2486 ;;;; Note modification
   2487 
   2488 ;;;;; Common helpers for note modifications
   2489 
   2490 (defun denote--file-types-with-extension (extension)
   2491   "Return only the entries of `denote-file-types' with EXTENSION.
   2492 See the format of `denote-file-types'."
   2493   (seq-filter (lambda (type)
   2494                 (string-equal (plist-get (cdr type) :extension) extension))
   2495               denote-file-types))
   2496 
   2497 (defun denote--file-type-org-capture-p ()
   2498   "Return non-nil if this is an `org-capture' buffer."
   2499   (and (bound-and-true-p org-capture-mode)
   2500        (derived-mode-p 'org-mode)
   2501        (string-match-p "\\`CAPTURE.*\\.org" (buffer-name))))
   2502 
   2503 (defun denote-filetype-heuristics (file)
   2504   "Return likely file type of FILE.
   2505 If in the process of `org-capture', consider the file type to be that of
   2506 Org.  Otherwise, use the file extension to detect the file type of FILE.
   2507 
   2508 If more than one file type correspond to this file extension, use the
   2509 first file type for which the :title-key-regexp in `denote-file-types'
   2510 matches in the file.
   2511 
   2512 If no file type in `denote-file-types' has the file extension,
   2513 the file type is assumed to be the first one in `denote-file-types'."
   2514   (cond
   2515    ((denote--file-type-org-capture-p) 'org)
   2516    (file
   2517     (let* ((extension (denote-get-file-extension-sans-encryption file))
   2518            (types (denote--file-types-with-extension extension)))
   2519       (cond ((null types)
   2520              (caar denote-file-types))
   2521             ((= (length types) 1)
   2522              (caar types))
   2523             (t
   2524              (or (car (seq-find
   2525                        (lambda (type)
   2526                          (denote--regexp-in-file-p (plist-get (cdr type) :title-key-regexp) file))
   2527                        types))
   2528                  (caar types))))))))
   2529 
   2530 (defun denote--file-attributes-time (file)
   2531   "Return `file-attribute-modification-time' of FILE as identifier."
   2532   (denote-get-identifier (file-attribute-modification-time (file-attributes file))))
   2533 
   2534 (defun denote--revert-dired (buf)
   2535   "Revert BUF if appropriate.
   2536 Do it if BUF is in Dired mode and is either part of the variable
   2537 `denote-directory' or the `current-buffer'."
   2538   (let ((current (current-buffer)))
   2539     (with-current-buffer buf
   2540       (when (and (eq major-mode 'dired-mode)
   2541                  (or (denote--dir-in-denote-directory-p default-directory)
   2542                      (eq current buf)))
   2543         (revert-buffer)))))
   2544 
   2545 (defun denote-update-dired-buffers ()
   2546   "Update Dired buffers of variable `denote-directory'.
   2547 Also revert the current Dired buffer even if it is not inside the
   2548 variable `denote-directory'."
   2549   (mapc #'denote--revert-dired (buffer-list)))
   2550 
   2551 (defun denote-rename-file-and-buffer (old-name new-name)
   2552   "Rename file named OLD-NAME to NEW-NAME, updating buffer name."
   2553   (unless (string= (expand-file-name old-name) (expand-file-name new-name))
   2554     (cond
   2555      ((derived-mode-p 'dired-mode)
   2556       (dired-rename-file old-name new-name nil))
   2557      ;; NOTE 2024-02-25: The `vc-rename-file' requires the file to be
   2558      ;; saved, but our convention is to not save the buffer after
   2559      ;; changing front matter unless we absolutely have to (allows
   2560      ;; users to do `diff-buffer-with-file', for example).
   2561      ((and denote-save-buffer-after-creation (not (buffer-modified-p)) (vc-backend old-name))
   2562       (vc-rename-file old-name new-name))
   2563      (t
   2564       (rename-file old-name new-name nil)))
   2565     (when-let ((buffer (find-buffer-visiting old-name)))
   2566       (with-current-buffer buffer
   2567         (set-visited-file-name new-name nil t)))))
   2568 
   2569 (defun denote--add-front-matter (file title keywords id file-type &optional save-buffer)
   2570   "Prepend front matter to FILE if `denote-file-is-note-p'.
   2571 The TITLE, KEYWORDS ID, and FILE-TYPE are passed from the
   2572 renaming command and are used to construct a new front matter
   2573 block if appropriate.
   2574 
   2575 With optional SAVE-BUFFER, save the buffer corresponding to FILE."
   2576   (when-let ((date (denote--date (date-to-time id) file-type))
   2577              (new-front-matter (denote--format-front-matter title date keywords id file-type)))
   2578     (with-current-buffer (find-file-noselect file)
   2579       (goto-char (point-min))
   2580       (insert new-front-matter)
   2581       (when save-buffer (save-buffer)))))
   2582 
   2583 (defun denote--regexp-in-file-p (regexp file)
   2584   "Return t if REGEXP matches in the FILE."
   2585   (denote--file-with-temp-buffer file
   2586     (re-search-forward regexp nil t 1)))
   2587 
   2588 (defun denote--edit-front-matter-p (file file-type)
   2589   "Test if FILE should be subject to front matter rewrite.
   2590 Use FILE-TYPE to look for the front matter lines.  This is
   2591 relevant for operations that insert or rewrite the front matter
   2592 in a Denote note.
   2593 
   2594 For the purposes of this test, FILE is a Denote note when it
   2595 contains a title line, a keywords line or both."
   2596   (and (denote--front-matter file-type)
   2597        (or (denote--regexp-in-file-p (denote--title-key-regexp file-type) file)
   2598            (denote--regexp-in-file-p (denote--keywords-key-regexp file-type) file))))
   2599 
   2600 (defun denote-rewrite-keywords (file keywords file-type &optional save-buffer)
   2601   "Rewrite KEYWORDS in FILE outright according to FILE-TYPE.
   2602 
   2603 Do the same as `denote-rewrite-front-matter' for keywords,
   2604 but do not ask for confirmation.
   2605 
   2606 With optional SAVE-BUFFER, save the buffer corresponding to FILE.
   2607 
   2608 This function is for use in the commands `denote-keywords-add',
   2609 `denote-keywords-remove', `denote-dired-rename-files', or
   2610 related."
   2611   (with-current-buffer (find-file-noselect file)
   2612     (save-excursion
   2613       (save-restriction
   2614         (widen)
   2615         (goto-char (point-min))
   2616         (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   2617           (goto-char (line-beginning-position))
   2618           (insert (denote--get-keywords-line-from-front-matter keywords file-type))
   2619           (delete-region (point) (line-end-position))
   2620           (when save-buffer (save-buffer)))))))
   2621 
   2622 (define-obsolete-function-alias
   2623   'denote--rewrite-keywords
   2624   'denote-rewrite-keywords
   2625   "2.0.0")
   2626 
   2627 (defun denote-rewrite-front-matter (file title keywords file-type &optional no-confirm)
   2628   "Rewrite front matter of note after `denote-rename-file'.
   2629 The FILE, TITLE, KEYWORDS, and FILE-TYPE are given by the
   2630 renaming command and are used to construct new front matter
   2631 values if appropriate.
   2632 
   2633 With optional NO-CONFIRM, do not prompt to confirm the rewriting
   2634 of the front matter.  Otherwise produce a `y-or-n-p' prompt to
   2635 that effect.
   2636 
   2637 With optional NO-CONFIRM, save the buffer after performing the
   2638 rewrite.  Otherwise leave it unsaved for furthter review by the
   2639 user."
   2640   (when-let ((old-title-line (denote-retrieve-front-matter-title-line file file-type))
   2641              (old-keywords-line (denote-retrieve-front-matter-keywords-line file file-type))
   2642              (new-title-line (denote--get-title-line-from-front-matter title file-type))
   2643              (new-keywords-line (denote--get-keywords-line-from-front-matter keywords file-type)))
   2644     (with-current-buffer (find-file-noselect file)
   2645       (when (or no-confirm
   2646                 (y-or-n-p (format
   2647                            "Replace front matter?\n-%s\n+%s\n\n-%s\n+%s?"
   2648                            (propertize old-title-line 'face 'denote-faces-prompt-old-name)
   2649                            (propertize new-title-line 'face 'denote-faces-prompt-new-name)
   2650                            (propertize old-keywords-line 'face 'denote-faces-prompt-old-name)
   2651                            (propertize new-keywords-line 'face 'denote-faces-prompt-new-name))))
   2652         (save-excursion
   2653           (save-restriction
   2654             (widen)
   2655             (goto-char (point-min))
   2656             (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   2657             (goto-char (line-beginning-position))
   2658             (insert new-title-line)
   2659             (delete-region (point) (line-end-position))
   2660             (goto-char (point-min))
   2661             (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   2662             (goto-char (line-beginning-position))
   2663             (insert new-keywords-line)
   2664             (delete-region (point) (line-end-position))
   2665             (when no-confirm (save-buffer))))))))
   2666 
   2667 (define-obsolete-function-alias
   2668   'denote--rewrite-front-matter
   2669   'denote-rewrite-front-matter
   2670   "2.0.0")
   2671 
   2672 ;;;;; The renaming commands and their prompts
   2673 
   2674 (defun denote--rename-dired-file-or-prompt ()
   2675   "Return Dired file at point, else prompt for one.
   2676 Throw error if FILE is not regular, else return FILE."
   2677   (or (dired-get-filename nil t)
   2678       (let* ((file (buffer-file-name))
   2679              (format (if file
   2680                          (format "Rename FILE Denote-style [%s]: " file)
   2681                        "Rename FILE Denote-style: "))
   2682              (selected-file (read-file-name format nil file t nil)))
   2683         (if (or (file-directory-p selected-file)
   2684                 (not (file-regular-p selected-file)))
   2685             (user-error "Only rename regular files")
   2686           selected-file))))
   2687 
   2688 (defun denote-rename-file-prompt (old-name new-name)
   2689   "Prompt to rename file named OLD-NAME to NEW-NAME."
   2690   (unless (string= (expand-file-name old-name) (expand-file-name new-name))
   2691     (y-or-n-p
   2692      (format "Rename %s to %s?"
   2693              (propertize (file-name-nondirectory old-name) 'face 'denote-faces-prompt-old-name)
   2694              (propertize (file-name-nondirectory new-name) 'face 'denote-faces-prompt-new-name)))))
   2695 
   2696 ;; NOTE 2023-10-20: We do not need a user option for this, though it
   2697 ;; can be useful to have it as a variable.
   2698 (defvar denote-rename-max-mini-window-height 0.33
   2699   "How much to enlarge `max-mini-window-height' for renaming operations.")
   2700 
   2701 ;;;###autoload
   2702 (defun denote-rename-file (file &optional title keywords signature date)
   2703   "Rename file and update existing front matter if appropriate.
   2704 
   2705 Always rename the file where it is located in the file system:
   2706 never move it to another directory.
   2707 
   2708 If in Dired, consider FILE to be the one at point, else prompt
   2709 with minibuffer completion for one.  When called from Lisp, FILE
   2710 is a file system path represented as a string.
   2711 
   2712 If FILE has a Denote-compliant identifier, retain it while
   2713 updating components of the file name referenced by the user
   2714 option `denote-prompts'.  By default, these are the TITLE and
   2715 KEYWORDS.  The SIGNATURE is another one.  When called from Lisp,
   2716 TITLE and SIGNATURE are strings, while KEYWORDS is a list of
   2717 strings.
   2718 
   2719 If there is no identifier, create an identifier based on the
   2720 following conditions:
   2721 
   2722 1. If the `denote-prompts' includes an entry for date prompts,
   2723    then prompt for DATE and take its input to produce a new
   2724    identifier.  For use in Lisp, DATE must conform with
   2725    `denote-valid-date-p'.
   2726 
   2727 2. If DATE is nil (e.g. when `denote-prompts' does not include a
   2728    date entry), use the file attributes to determine the last
   2729    modified date of FILE and format it as an identifier.
   2730 
   2731 3. As a fallback, derive an identifier from the current date and
   2732    time.
   2733 
   2734 4. At any rate, if the resulting identifier is not unique among
   2735    the files in the variable `denote-directory', increment it
   2736    such that it becomes unique.
   2737 
   2738 In interactive use, and assuming `denote-prompts' includes a
   2739 title entry, make the TITLE prompt have prefilled text in the
   2740 minibuffer that consists of the current title of FILE.  The
   2741 current title is either retrieved from the front matter (such as
   2742 the #+title in Org) or from the file name.
   2743 
   2744 Do the same for the SIGNATURE prompt, subject to `denote-prompts',
   2745 by prefilling the minibuffer with the current signature of FILE,
   2746 if any.
   2747 
   2748 Same principle for the KEYWORDS prompt: convert the keywords in
   2749 the file name into a comma-separated string and prefill the
   2750 minibuffer with it (the KEYWORDS prompt accepts more than one
   2751 keywords, each separated by a comma, else the `crm-separator').
   2752 
   2753 For all prompts, interpret an empty input as an instruction to
   2754 remove that file name component.  For example, if a TITLE prompt
   2755 is available and FILE is 20240211T093531--some-title__keyword1.org
   2756 then rename FILE to 20240211T093531__keyword1.org.
   2757 
   2758 If a file name component is present, but there is no entry for it in
   2759 `denote-prompts', keep it as-is.
   2760 
   2761 [ NOTE: Please check with your minibuffer user interface how to
   2762   provide an empty input.  The Emacs default setup accepts the
   2763   empty minibuffer contents as they are, though popular packages
   2764   like `vertico' use the first available completion candidate
   2765   instead.  For `vertico', the user must either move one up to
   2766   select the prompt and then type RET there with empty contents,
   2767   or use the command `vertico-exit-input' with empty contents.
   2768   That Vertico command is bound to M-RET as of this writing on
   2769   2024-02-13 08:08 +0200. ]
   2770 
   2771 When renaming FILE, read its file type extension (like .org) and
   2772 preserve it through the renaming process.  Files that have no
   2773 extension are left without one.
   2774 
   2775 As a final step, ask for confirmation, showing the difference
   2776 between old and new file names.  Do not ask for confirmation if
   2777 the user option `denote-rename-no-confirm' is set to a non-nil
   2778 value.
   2779 
   2780 If FILE has front matter for TITLE and KEYWORDS, ask to rewrite
   2781 their values in order to reflect the new input, unless
   2782 `denote-rename-no-confirm' is non-nil.  When the
   2783 `denote-rename-no-confirm' is nil (the default), do not save the
   2784 underlying buffer, thus giving the user the option to
   2785 double-check the result, such as by invokling the command
   2786 `diff-buffer-with-file'.  The rewrite of the TITLE and KEYWORDS
   2787 in the front matter should not affect the rest of the front
   2788 matter.
   2789 
   2790 If the file does not have front matter but is among the supported
   2791 file types (per `denote-file-type'), add front matter to the top
   2792 of it and leave the buffer unsaved for further inspection.  Save
   2793 the buffer if `denote-rename-no-confirm' is non-nil.
   2794 
   2795 For the front matter of each file type, refer to the variables:
   2796 
   2797 - `denote-org-front-matter'
   2798 - `denote-text-front-matter'
   2799 - `denote-toml-front-matter'
   2800 - `denote-yaml-front-matter'
   2801 
   2802 Run the `denote-after-rename-file-hook' after renaming FILE.
   2803 
   2804 This command is intended to (i) rename Denote files, (ii) convert
   2805 existing supported file types to Denote notes, and (ii) rename
   2806 non-note files (e.g. PDF) that can benefit from Denote's
   2807 file-naming scheme.
   2808 
   2809 For a version of this command that works with multiple files
   2810 one-by-one, use `denote-dired-rename-files'."
   2811   (interactive
   2812    (let* ((file (denote--rename-dired-file-or-prompt))
   2813           (file-type (denote-filetype-heuristics file))
   2814           (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name))
   2815           (date nil)
   2816           (title (denote-retrieve-title-or-filename file file-type))
   2817           (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords file) "")))
   2818           (signature (or (denote-retrieve-filename-signature file) "")))
   2819      (dolist (prompt denote-prompts)
   2820        (pcase prompt
   2821          ('title
   2822           (setq title (denote-title-prompt
   2823                        title
   2824                        (format "Rename `%s' with TITLE (empty to remove)" file-in-prompt))))
   2825          ('keywords
   2826           (setq keywords (denote-keywords-prompt
   2827                           (format "Rename `%s' with KEYWORDS (empty to remove)" file-in-prompt)
   2828                           keywords)))
   2829          ('signature
   2830           (setq signature (denote-signature-prompt
   2831                            signature
   2832                            (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt))))
   2833          ('date
   2834           (unless (denote-file-has-identifier-p file)
   2835             (setq date (denote-date-prompt))))))
   2836      (list file title keywords signature date)))
   2837   (setq keywords (denote-keywords-sort
   2838                   (if (stringp keywords)
   2839                       (split-string keywords "," :omit-nulls)
   2840                     keywords)))
   2841   (let* ((dir (file-name-directory file))
   2842          (id (or (denote-retrieve-filename-identifier file)
   2843                  (denote-create-unique-file-identifier file (denote--get-all-used-ids) date)))
   2844          ;; TODO 2024-02-13: Should we derive the extension from the
   2845          ;; `denote-file-type-prompt' if we are conforming with the
   2846          ;; `denote-prompts'?
   2847          (extension (denote-get-file-extension file))
   2848          (file-type (denote-filetype-heuristics file))
   2849          (new-name (denote-format-file-name dir id keywords title extension signature))
   2850          (max-mini-window-height denote-rename-max-mini-window-height))
   2851     (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name))
   2852       (denote-rename-file-and-buffer file new-name)
   2853       (denote-update-dired-buffers)
   2854       (when (denote-file-is-writable-and-supported-p new-name)
   2855         (if (denote--edit-front-matter-p new-name file-type)
   2856             (denote-rewrite-front-matter new-name title keywords file-type denote-rename-no-confirm)
   2857           (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm)))
   2858       (run-hooks 'denote-after-rename-file-hook))
   2859     new-name))
   2860 
   2861 ;;;###autoload
   2862 (defun denote-dired-rename-files ()
   2863   "Rename Dired marked files same way as `denote-rename-file'.
   2864 Rename each file in sequence, making all the relevant prompts.
   2865 Unlike `denote-rename-file', do not prompt for confirmation of
   2866 the changes made to the file: perform them outright (same as
   2867 setting `denote-rename-no-confirm' to a non-nil value)."
   2868   (declare (interactive-only t))
   2869   (interactive nil dired-mode)
   2870   (if-let ((marks (dired-get-marked-files)))
   2871       (let ((used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks)
   2872                         (denote--get-all-used-ids))))
   2873         (dolist (file marks)
   2874           (let* ((file-type (denote-filetype-heuristics file))
   2875                  (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name))
   2876                  (dir (file-name-directory file))
   2877                  (id (or (denote-retrieve-filename-identifier file)
   2878                          (denote-create-unique-file-identifier file used-ids)))
   2879                  (title (denote-retrieve-title-or-filename file file-type))
   2880                  (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords file) "")))
   2881                  (signature (or (denote-retrieve-filename-signature file) ""))
   2882                  (extension (denote-get-file-extension file)))
   2883             (dolist (prompt denote-prompts)
   2884               (pcase prompt
   2885                 ('title
   2886                  (setq title (denote-title-prompt
   2887                               title
   2888                               (format "Rename `%s' with TITLE (empty to remove)" file-in-prompt))))
   2889                 ('keywords
   2890                  (setq keywords (denote-keywords-prompt
   2891                                  (format "Rename `%s' with KEYWORDS (empty to remove)" file-in-prompt)
   2892                                  keywords)))
   2893                 ('signature
   2894                  (setq signature (denote-signature-prompt
   2895                                   signature
   2896                                   (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt))))
   2897                 ('date
   2898                  (setq id (denote-prompt-for-date-return-id)))))
   2899             (setq keywords (denote-keywords-sort
   2900                             (if (stringp keywords)
   2901                                 (split-string keywords "," :omit-nulls)
   2902                               keywords)))
   2903             (let ((new-name (denote-format-file-name dir id keywords title extension signature)))
   2904               (denote-rename-file-and-buffer file new-name)
   2905               (when (denote-file-is-writable-and-supported-p new-name)
   2906                 (if (denote--edit-front-matter-p new-name file-type)
   2907                     (denote-rewrite-front-matter new-name title keywords file-type :no-confirm)
   2908                   (denote--add-front-matter new-name title keywords id file-type :save-buffer)))
   2909               (run-hooks 'denote-after-rename-file-hook)
   2910               (when used-ids
   2911                 (puthash id t used-ids)))))
   2912         (denote-update-dired-buffers))
   2913     (user-error "No marked files; aborting")))
   2914 
   2915 (make-obsolete
   2916  'denote-dired-rename-marked-files
   2917  'denote-dired-rename-marked-files-with-keywords
   2918  "2.1.0")
   2919 
   2920 (defalias 'denote-dired-rename-marked-files 'denote-dired-rename-files
   2921   "Alias for `denote-dired-rename-files'.")
   2922 
   2923 ;;;###autoload
   2924 (defun denote-dired-rename-marked-files-with-keywords ()
   2925   "Rename marked files in Dired to a Denote file name by writing keywords.
   2926 
   2927 Specifically, do the following:
   2928 
   2929 - retain the file's existing name and make it the TITLE field,
   2930   per Denote's file-naming scheme;
   2931 
   2932 - sluggify the TITLE, according to our conventions (check the
   2933   user option `denote-file-name-slug-functions');
   2934 
   2935 - prepend an identifier to the TITLE;
   2936 
   2937 - preserve the file's extension, if any;
   2938 
   2939 - prompt once for KEYWORDS and apply the user's input to the
   2940   corresponding field in the file name, rewriting any keywords
   2941   that may exist while removing keywords that do exist if
   2942   KEYWORDS is empty;
   2943 
   2944 - add or rewrite existing front matter to the underlying file, if
   2945   it is recognized as a Denote note (per `denote-file-type'),
   2946   such that it includes the new keywords.
   2947 
   2948 Run the `denote-after-rename-file-hook' after renaming is done.
   2949 
   2950 [ Note that the affected buffers are not saved, unless the user
   2951   option `denote-rename-no-confirm' is non-nil.  Users can thus
   2952   check them to confirm that the new front matter does not cause
   2953   any problems (e.g. with the `diff-buffer-with-file' command).
   2954   Multiple buffers can be saved in one go with the command
   2955   `save-some-buffers' (read its doc string).  ]"
   2956   (declare (interactive-only t))
   2957   (interactive nil dired-mode)
   2958   (if-let ((marks (dired-get-marked-files)))
   2959       (let ((keywords (denote-keywords-sort
   2960                        (denote-keywords-prompt "Rename marked files with KEYWORDS, overwriting existing (empty to ignore/remove)")))
   2961             (used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks)
   2962                         (denote--get-all-used-ids))))
   2963         (dolist (file marks)
   2964           (let* ((dir (file-name-directory file))
   2965                  (id (or (denote-retrieve-filename-identifier file)
   2966                          (denote-create-unique-file-identifier file used-ids)))
   2967                  (signature (or (denote-retrieve-filename-signature file) ""))
   2968                  (file-type (denote-filetype-heuristics file))
   2969                  (title (denote-retrieve-title-or-filename file file-type))
   2970                  (extension (denote-get-file-extension file))
   2971                  (new-name (denote-format-file-name dir id keywords title extension signature)))
   2972             (denote-rename-file-and-buffer file new-name)
   2973             (when (denote-file-is-writable-and-supported-p new-name)
   2974               (if (denote--edit-front-matter-p new-name file-type)
   2975                   (denote-rewrite-keywords new-name keywords file-type denote-rename-no-confirm)
   2976                 (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm)))
   2977             (run-hooks 'denote-after-rename-file-hook)
   2978             (when used-ids
   2979               (puthash id t used-ids))))
   2980         (denote-update-dired-buffers))
   2981     (user-error "No marked files; aborting")))
   2982 
   2983 ;;;###autoload
   2984 (defun denote-rename-file-using-front-matter (file &optional no-confirm save-buffer)
   2985   "Rename FILE using its front matter as input.
   2986 When called interactively, FILE is the return value of the
   2987 function `buffer-file-name' which is subsequently inspected for
   2988 the requisite front matter.  It is thus implied that the FILE has
   2989 a file type that is supported by Denote, per `denote-file-type'.
   2990 
   2991 Unless NO-CONFIRM is non-nil (such as with a prefix argument),
   2992 ask for confirmation, showing the difference between the old and
   2993 the new file names.
   2994 
   2995 Never modify the identifier of the FILE, if any, even if it is
   2996 edited in the front matter.  Denote considers the file name to be
   2997 the source of truth in this case to avoid potential breakage with
   2998 typos and the like.
   2999 
   3000 If NO-CONFIRM is non-nil (such as with a prefix argument) do not
   3001 prompt for confirmation while renaming the file.  Do it outright.
   3002 
   3003 If optional SAVE-BUFFER is non-nil (such as with a double prefix
   3004 argument), save the corresponding buffer.
   3005 
   3006 If the user option `denote-rename-no-confirm' is non-nil,
   3007 interpret it the same way as a combination of NO-CONFIRM and
   3008 SAVE-BUFFER.
   3009 
   3010 The identifier of the file, if any, is never modified even if it
   3011 is edited in the front matter: Denote considers the file name to
   3012 be the source of truth in this case, to avoid potential breakage
   3013 with typos and the like."
   3014   (interactive
   3015    (let (no-confirm save-buffer)
   3016      (cond
   3017       ((and current-prefix-arg (> (prefix-numeric-value current-prefix-arg) 4))
   3018        (setq no-confirm t
   3019              save-buffer t))
   3020       (current-prefix-arg
   3021        (setq no-confirm t)))
   3022      (list buffer-file-name no-confirm save-buffer)))
   3023   (unless (denote-file-is-writable-and-supported-p file)
   3024     (user-error "The file is not writable or does not have a supported file extension"))
   3025   (if-let ((file-type (denote-filetype-heuristics file))
   3026            (title (denote-retrieve-front-matter-title-value file file-type))
   3027            (id (denote-retrieve-filename-identifier file)))
   3028       (let* ((keywords (denote-retrieve-front-matter-keywords-value file file-type))
   3029              (signature (or (denote-retrieve-filename-signature file) ""))
   3030              (extension (denote-get-file-extension file))
   3031              (dir (file-name-directory file))
   3032              (new-name (denote-format-file-name dir id keywords title extension signature)))
   3033         (when (or denote-rename-no-confirm
   3034                   no-confirm
   3035                   (denote-rename-file-prompt file new-name))
   3036           (denote-rename-file-and-buffer file new-name)
   3037           (denote-update-dired-buffers)
   3038           (when (or denote-rename-no-confirm save-buffer)
   3039             (save-buffer))
   3040           (run-hooks 'denote-after-rename-file-hook)))
   3041     (user-error "No identifier or front matter for title")))
   3042 
   3043 ;;;###autoload
   3044 (defun denote-dired-rename-marked-files-using-front-matter ()
   3045   "Call `denote-rename-file-using-front-matter' over the Dired marked files.
   3046 Refer to the documentation of that command for the technicalities.
   3047 
   3048 Marked files must count as notes for the purposes of Denote,
   3049 which means that they at least have an identifier in their file
   3050 name and use a supported file type, per `denote-file-type'.
   3051 Files that do not meet this criterion are ignored because Denote
   3052 cannot know if they have front matter and what that may be."
   3053   (interactive nil dired-mode)
   3054   (if-let ((marks (seq-filter
   3055                    (lambda (m)
   3056                      (and (denote-file-is-writable-and-supported-p m)
   3057                           (denote-file-has-identifier-p m)))
   3058                    (dired-get-marked-files))))
   3059       (progn
   3060         (dolist (file marks)
   3061           (denote-rename-file-using-front-matter file :no-confirm denote-rename-no-confirm))
   3062         (denote-update-dired-buffers))
   3063     (user-error "No marked Denote files; aborting")))
   3064 
   3065 ;;;;;; Interactively modify keywords and rename accordingly
   3066 
   3067 ;;;###autoload
   3068 (defun denote-keywords-add (keywords &optional save-buffer)
   3069   "Prompt for KEYWORDS to add to the current note's front matter.
   3070 When called from Lisp, KEYWORDS is a list of strings.
   3071 
   3072 Rename the file without further prompt so that its name reflects
   3073 the new front matter, per `denote-rename-file-using-front-matter'.
   3074 
   3075 With an optional SAVE-BUFFER (such as a prefix argument when
   3076 called interactively), save the buffer outright.  Otherwise leave
   3077 the buffer unsaved for further review.
   3078 
   3079 If the user option `denote-rename-no-confirm' is non-nil,
   3080 interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER
   3081 reduntant.
   3082 
   3083 Run `denote-after-rename-file-hook' as a final step."
   3084   (interactive (list (denote-keywords-prompt "Add KEYWORDS") current-prefix-arg))
   3085   ;; A combination of if-let and let, as we need to take into account
   3086   ;; the scenario in which there are no keywords yet.
   3087   (if-let ((file (buffer-file-name))
   3088            ((denote-file-is-note-p file))
   3089            (file-type (denote-filetype-heuristics file)))
   3090       (let* ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type))
   3091              (new-keywords (denote-keywords-sort
   3092                             (seq-uniq (append keywords cur-keywords)))))
   3093         (denote-rewrite-keywords file new-keywords file-type)
   3094         (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer))
   3095         (run-hooks 'denote-after-rename-file-hook))
   3096     (user-error "Buffer not visiting a Denote file")))
   3097 
   3098 (defalias 'denote-rename-add-keywords 'denote-keywords-add
   3099   "Alias for `denote-keywords-add'.")
   3100 
   3101 (defun denote--keywords-delete-prompt (keywords)
   3102   "Prompt for one or more KEYWORDS.
   3103 In the case of multiple entries, those are separated by the
   3104 `crm-separator', which typically is a comma.  In such a case, the
   3105 output is sorted with `string-collate-lessp'."
   3106   (let ((choice (denote--keywords-crm keywords "Keywords to remove")))
   3107     (if denote-sort-keywords
   3108         (sort choice #'string-collate-lessp)
   3109       choice)))
   3110 
   3111 ;;;###autoload
   3112 (defun denote-keywords-remove (&optional save-buffer)
   3113   "Prompt for keywords in current note and remove them.
   3114 Keywords are retrieved from the file's front matter.
   3115 
   3116 Rename the file without further prompt so that its name reflects
   3117 the new front matter, per `denote-rename-file-using-front-matter'.
   3118 
   3119 With an optional SAVE-BUFFER as a prefix argument, save the
   3120 buffer outright.  Otherwise leave the buffer unsaved for further
   3121 review.
   3122 
   3123 If the user option `denote-rename-no-confirm' is non-nil,
   3124 interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER
   3125 reduntant.
   3126 
   3127 Run `denote-after-rename-file-hook' as a final step."
   3128   (declare (interactive-only t))
   3129   (interactive "P")
   3130   (if-let ((file (buffer-file-name))
   3131            ((denote-file-is-note-p file))
   3132            (file-type (denote-filetype-heuristics file)))
   3133       (when-let ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type))
   3134                  (del-keyword (denote--keywords-delete-prompt cur-keywords)))
   3135         (denote-rewrite-keywords
   3136          file
   3137          (seq-difference cur-keywords del-keyword)
   3138          file-type)
   3139         (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer))
   3140         (run-hooks 'denote-after-rename-file-hook))
   3141     (user-error "Buffer not visiting a Denote file")))
   3142 
   3143 (defalias 'denote-rename-remove-keywords 'denote-keywords-remove
   3144   "Alias for `denote-keywords-remove'.")
   3145 
   3146 ;;;;;; Interactively add or remove file name signature
   3147 
   3148 ;;;###autoload
   3149 (defun denote-rename-add-signature (file signature)
   3150   "Add to FILE name the SIGNATURE.
   3151 In interactive use, prompt for FILE, defaulting either to the current
   3152 buffer's file or the one at point in a Dired buffer.  Also prompt for
   3153 SIGNATURE, using the existing one, if any, as the initial value.
   3154 
   3155 When called from Lisp, FILE is a string pointing to a file system path
   3156 and SIGNATURE is a string.
   3157 
   3158 Ask for confirmation before renaming the file to include the new
   3159 signature.  Do it unless the user option `denote-rename-no-confirm' is
   3160 set to a non-nil value.
   3161 
   3162 Once the operation is done, reload any Dired buffers and run the
   3163 `denote-after-rename-file-hook'.
   3164 
   3165 Also see `denote-rename-remove-signature'."
   3166   (interactive
   3167    (let* ((file (denote--rename-dired-file-or-prompt))
   3168           (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)))
   3169      (list
   3170       file
   3171       (denote-signature-prompt
   3172        (or (denote-retrieve-filename-signature file) "")
   3173        (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))))
   3174   (let* ((type (denote-filetype-heuristics file))
   3175          (title (denote-retrieve-title-or-filename file type))
   3176          (keywords-string (denote-retrieve-filename-keywords file))
   3177          (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_")))
   3178          (dir (file-name-directory file))
   3179          (id (or (denote-retrieve-filename-identifier file)
   3180                  (denote-create-unique-file-identifier file (denote--get-all-used-ids))))
   3181          (extension (denote-get-file-extension file))
   3182          (new-name (denote-format-file-name dir id keywords title extension signature)))
   3183     (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name))
   3184       (denote-rename-file-and-buffer file new-name)
   3185       (denote-update-dired-buffers)
   3186       (run-hooks 'denote-after-rename-file-hook))))
   3187 
   3188 ;;;###autoload
   3189 (defun denote-rename-remove-signature (file)
   3190   "Remove the signature of FILE.
   3191 In interactive use, prompt for FILE, defaulting either to the current
   3192 buffer's file or the one at point in a Dired buffer.  When called from
   3193 Lisp, FILE is a string pointing to a file system path.
   3194 
   3195 Ask for confirmation before renaming the file to remove its signature.
   3196 Do it unless the user option `denote-rename-no-confirm' is set to a
   3197 non-nil value.
   3198 
   3199 Once the operation is done, reload any Dired buffers and run the
   3200 `denote-after-rename-file-hook'.
   3201 
   3202 Also see `denote-rename-add-signature'."
   3203   (interactive (list (denote--rename-dired-file-or-prompt)))
   3204   (when (denote-retrieve-filename-signature file)
   3205     (let* ((type (denote-filetype-heuristics file))
   3206            (title (denote-retrieve-title-or-filename file type))
   3207            (keywords-string (denote-retrieve-filename-keywords file))
   3208            (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_")))
   3209            (dir (file-name-directory file))
   3210            (id (or (denote-retrieve-filename-identifier file)
   3211                    (denote-create-unique-file-identifier file (denote--get-all-used-ids))))
   3212            (extension (denote-get-file-extension file))
   3213            (new-name (denote-format-file-name dir id keywords title extension nil)))
   3214       (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name))
   3215         (denote-rename-file-and-buffer file new-name)
   3216         (denote-update-dired-buffers)
   3217         (run-hooks 'denote-after-rename-file-hook)))))
   3218 
   3219 ;;;;; Creation of front matter
   3220 
   3221 ;;;###autoload
   3222 (defun denote-add-front-matter (file title keywords)
   3223   "Insert front matter at the top of FILE.
   3224 
   3225 When called interactively, FILE is the return value of the
   3226 function `buffer-file-name'.  FILE is checked to determine
   3227 whether it is a note for Denote's purposes.
   3228 
   3229 TITLE is a string.  Interactively, it is the user input at the
   3230 minibuffer prompt.
   3231 
   3232 KEYWORDS is a list of strings.  Interactively, it is the user
   3233 input at the minibuffer prompt.  This one supports completion for
   3234 multiple entries, each separated by the `crm-separator' (normally
   3235 a comma).
   3236 
   3237 The purpose of this command is to help the user generate new
   3238 front matter for an existing note (perhaps because the user
   3239 deleted the previous one and could not undo the change).
   3240 
   3241 This command does not rename the file (e.g. to update the
   3242 keywords).  To rename a file by reading its front matter as
   3243 input, use `denote-rename-file-using-front-matter'.
   3244 
   3245 Note that this command is useful only for existing Denote notes.
   3246 If the user needs to convert a generic text file to a Denote
   3247 note, they can use one of the command which first rename the file
   3248 to make it comply with our file-naming scheme and then add the
   3249 relevant front matter.
   3250 
   3251 [ NOTE: Please check with your minibuffer user interface how to
   3252   provide an empty input.  The Emacs default setup accepts the
   3253   empty minibuffer contents as they are, though popular packages
   3254   like `vertico' use the first available completion candidate
   3255   instead.  For `vertico', the user must either move one up to
   3256   select the prompt and then type RET there with empty contents,
   3257   or use the command `vertico-exit-input' with empty contents.
   3258   That Vertico command is bound to M-RET as of this writing on
   3259   2024-02-29 09:24 +0200. ]"
   3260   (interactive
   3261    (list
   3262     (buffer-file-name)
   3263     (denote-title-prompt nil "Add TITLE (empty to ignore)")
   3264     (denote-keywords-sort (denote-keywords-prompt "Add KEYWORDS (empty to ignore)"))))
   3265   (when-let ((denote-file-is-writable-and-supported-p file)
   3266              (id (denote-retrieve-filename-identifier file))
   3267              (file-type (denote-filetype-heuristics file)))
   3268     (denote--add-front-matter file title keywords id file-type)))
   3269 
   3270 (define-obsolete-function-alias
   3271   'denote-change-file-type
   3272   'denote-change-file-type-and-front-matter
   3273   "2.1.0")
   3274 
   3275 ;;;###autoload
   3276 (defun denote-change-file-type-and-front-matter (file new-file-type)
   3277   "Change file type of FILE and add an appropriate front matter.
   3278 
   3279 If in Dired, consider FILE to be the one at point, else prompt
   3280 with minibuffer completion for one.
   3281 
   3282 Add a front matter in the format of the NEW-FILE-TYPE at the
   3283 beginning of the file.
   3284 
   3285 Retrieve the title of FILE from a line starting with a title
   3286 field in its front matter, depending on the previous file
   3287 type (e.g.  #+title for Org).  The same process applies for
   3288 keywords.
   3289 
   3290 As a final step, ask for confirmation, showing the difference
   3291 between old and new file names.
   3292 
   3293 Important note: No attempt is made to modify any other elements
   3294 of the file.  This needs to be done manually."
   3295   (interactive
   3296    (list
   3297     (denote--rename-dired-file-or-prompt)
   3298     (denote--valid-file-type (or (denote-file-type-prompt) denote-file-type))))
   3299   (let* ((dir (file-name-directory file))
   3300          (old-file-type (denote-filetype-heuristics file))
   3301          (id (or (denote-retrieve-filename-identifier file) ""))
   3302          (title (denote-retrieve-title-or-filename file old-file-type))
   3303          (keywords (denote-retrieve-front-matter-keywords-value file old-file-type))
   3304          (signature (or (denote-retrieve-filename-signature file) ""))
   3305          (old-extension (denote-get-file-extension file))
   3306          (new-extension (denote--file-extension new-file-type))
   3307          (new-name (denote-format-file-name dir id keywords title new-extension signature))
   3308          (max-mini-window-height denote-rename-max-mini-window-height))
   3309     (when (and (not (eq old-extension new-extension))
   3310                (denote-rename-file-prompt file new-name))
   3311       (denote-rename-file-and-buffer file new-name)
   3312       (denote-update-dired-buffers)
   3313       (when (denote-file-is-writable-and-supported-p new-name)
   3314         (denote--add-front-matter new-name title keywords id new-file-type)))))
   3315 
   3316 ;;;; The Denote faces
   3317 
   3318 (defgroup denote-faces ()
   3319   "Faces for Denote."
   3320   :group 'denote)
   3321 
   3322 (defface denote-faces-link '((t :inherit link))
   3323   "Face used to style Denote links in the buffer."
   3324   :group 'denote-faces
   3325   :package-version '(denote . "0.5.0"))
   3326 
   3327 (defface denote-faces-subdirectory '((t :inherit bold))
   3328   "Face for subdirectory of file name.
   3329 This should only ever needed in the backlinks' buffer (or
   3330 equivalent), not in Dired."
   3331   :group 'denote-faces
   3332   :package-version '(denote . "0.2.0"))
   3333 
   3334 (defface denote-faces-date '((t :inherit font-lock-variable-name-face))
   3335   "Face for file name date in Dired buffers.
   3336 This is the part of the identifier that covers the year, month,
   3337 and day."
   3338   :group 'denote-faces
   3339   :package-version '(denote . "0.1.0"))
   3340 
   3341 (defface denote-faces-time '((t :inherit denote-faces-date))
   3342   "Face for file name time in Dired buffers.
   3343 This is the part of the identifier that covers the hours, minutes,
   3344 and seconds."
   3345   :group 'denote-faces
   3346   :package-version '(denote . "0.1.0"))
   3347 
   3348 (defface denote-faces-title nil
   3349   "Face for file name title in Dired buffers."
   3350   :group 'denote-faces
   3351   :package-version '(denote . "0.1.0"))
   3352 
   3353 (defface denote-faces-year '((t :inherit denote-faces-date))
   3354   "Face for file name year in Dired buffers.
   3355 This is the part of the identifier that covers the year, month, and day."
   3356   :group 'denote-faces
   3357   :package-version '(denote . "2.3.0"))
   3358 
   3359 (defface denote-faces-month '((t :inherit denote-faces-date))
   3360   "Face for file name month in Dired buffers.
   3361 This is the part of the identifier that covers the year, month, and day."
   3362   :group 'denote-faces
   3363   :package-version '(denote . "2.3.0"))
   3364 
   3365 (defface denote-faces-day '((t :inherit denote-faces-date))
   3366   "Face for file name day in Dired buffers.
   3367 This is the part of the identifier that covers the year, month, and day."
   3368   :group 'denote-faces
   3369   :package-version '(denote . "2.3.0"))
   3370 
   3371 (defface denote-faces-hour '((t :inherit denote-faces-date))
   3372   "Face for file name hours in Dired buffers.
   3373 This is the part of the identifier that covers the hours, minutes,
   3374 and seconds."
   3375   :group 'denote-faces
   3376   :package-version '(denote . "2.3.0"))
   3377 
   3378 (defface denote-faces-minute '((t :inherit denote-faces-date))
   3379   "Face for file name minutes in Dired buffers.
   3380 This is the part of the identifier that covers the hours, minutes,
   3381 and seconds."
   3382   :group 'denote-faces
   3383   :package-version '(denote . "2.3.0"))
   3384 
   3385 (defface denote-faces-second '((t :inherit denote-faces-date))
   3386   "Face for file name seconds in Dired buffers.
   3387 This is the part of the identifier that covers the hours, minutes,
   3388 and seconds."
   3389   :group 'denote-faces
   3390   :package-version '(denote . "2.3.0"))
   3391 
   3392 (defface denote-faces-extension '((t :inherit shadow))
   3393   "Face for file extension type in Dired buffers."
   3394   :group 'denote-faces
   3395   :package-version '(denote . "0.1.0"))
   3396 
   3397 (defface denote-faces-keywords '((t :inherit font-lock-builtin-face))
   3398   "Face for file name keywords in Dired buffers."
   3399   :group 'denote-faces
   3400   :package-version '(denote . "0.1.0"))
   3401 
   3402 (defface denote-faces-signature '((t :inherit font-lock-warning-face))
   3403   "Face for file name signature in Dired buffers."
   3404   :group 'denote-faces
   3405   :package-version '(denote . "2.0.0"))
   3406 
   3407 (defface denote-faces-delimiter
   3408   '((((class color) (min-colors 88) (background light))
   3409      :foreground "gray70")
   3410     (((class color) (min-colors 88) (background dark))
   3411      :foreground "gray30")
   3412     (t :inherit shadow))
   3413   "Face for file name delimiters in Dired buffers."
   3414   :group 'denote-faces
   3415   :package-version '(denote . "0.1.0"))
   3416 
   3417 (defface denote-faces-time-delimiter '((t :inherit shadow))
   3418   "Face for the delimiter between date and time in Dired buffers."
   3419   :group 'denote-faces
   3420   :package-version '(denote . "2.1.0"))
   3421 
   3422 (defvar denote-faces--file-name-regexp
   3423   (concat "\\(?11:[\t\s]+\\|.*/\\)?"
   3424           "\\(?1:[0-9]\\{4\\}\\)\\(?12:[0-9]\\{2\\}\\)\\(?13:[0-9]\\{2\\}\\)"
   3425           "\\(?10:T\\)"
   3426           "\\(?2:[0-9]\\{2\\}\\)\\(?14:[0-9]\\{2\\}\\)\\(?15:[0-9]\\{2\\}\\)"
   3427           "\\(?:\\(?3:==\\)\\(?4:[^.]*?\\)\\)?"
   3428           "\\(?:\\(?5:--\\)\\(?6:[^.]*?\\)\\)?"
   3429           "\\(?:\\(?7:__\\)\\(?8:[^.]*?\\)\\)?"
   3430           "\\(?9:\\..*\\)?$")
   3431   "Regexp of file names for fontification.")
   3432 
   3433 (defconst denote-faces-file-name-keywords
   3434   `((,denote-faces--file-name-regexp
   3435      (11 'denote-faces-subdirectory nil t)
   3436      (1 'denote-faces-year nil t)
   3437      (12 'denote-faces-month nil t)
   3438      (13 'denote-faces-day nil t)
   3439      (10 'denote-faces-time-delimiter nil t)
   3440      (2 'denote-faces-hour nil t)
   3441      (14 'denote-faces-minute nil t)
   3442      (15 'denote-faces-second nil t)
   3443      (3 'denote-faces-delimiter nil t)
   3444      (4 'denote-faces-signature nil t)
   3445      (5 'denote-faces-delimiter nil t)
   3446      (6 'denote-faces-title nil t)
   3447      (7 'denote-faces-delimiter nil t)
   3448      (8 'denote-faces-keywords nil t)
   3449      (9 'denote-faces-extension nil t )))
   3450   "Keywords for fontification of file names.")
   3451 
   3452 (make-obsolete-variable 'denote-faces-file-name-keywords-for-backlinks nil "2.2.0")
   3453 
   3454 (defface denote-faces-prompt-old-name '((t :inherit error))
   3455   "Face for the old name shown in the prompt of `denote-rename-file' etc."
   3456   :group 'denote-faces
   3457   :package-version '(denote . "2.2.0"))
   3458 
   3459 (defface denote-faces-prompt-new-name '((t :inherit success))
   3460   "Face for the new name shown in the prompt of `denote-rename-file' etc."
   3461   :group 'denote-faces
   3462   :package-version '(denote . "2.2.0"))
   3463 
   3464 (defface denote-faces-prompt-current-name '((t :inherit denote-faces-prompt-old-name))
   3465   "Face for the current file shown in the prompt of `denote-rename-file' etc."
   3466   :group 'denote-faces
   3467   :package-version '(denote . "2.2.0"))
   3468 
   3469 ;;;; Fontification in Dired
   3470 
   3471 (defgroup denote-dired ()
   3472   "Integration between Denote and Dired."
   3473   :group 'denote)
   3474 
   3475 (defcustom denote-dired-directories (list denote-directory)
   3476   "List of directories where `denote-dired-mode' should apply to.
   3477 For this to take effect, add `denote-dired-mode-in-directories',
   3478 to the `dired-mode-hook'.
   3479 
   3480 If `denote-dired-directories-include-subdirectories' is non-nil,
   3481 also apply the effect to all subdirectories of those specified in
   3482 the list."
   3483   :type '(repeat directory)
   3484   :package-version '(denote . "0.1.0")
   3485   :link '(info-link "(denote) Fontification in Dired")
   3486   :group 'denote-dired)
   3487 
   3488 (defcustom denote-dired-directories-include-subdirectories nil
   3489   "If non-nil `denote-dired-directories' also affects all subdirectories.
   3490 Otherwise `denote-dired-directories' works only with exact matches."
   3491   :package-version '(denote . "2.2.0")
   3492   :link '(info-link "(denote) Fontification in Dired")
   3493   :type 'boolean
   3494   :group 'denote-dired)
   3495 
   3496 ;; FIXME 2022-08-12: Make `denote-dired-mode' work with diredfl.  This
   3497 ;; may prove challenging.
   3498 
   3499 (defun denote-dired-add-font-lock (&rest _)
   3500   "Append `denote-faces-file-name-keywords' to font lock keywords."
   3501   ;; NOTE 2023-10-28: I tried to add the first argument and then
   3502   ;; experimented with various combinations of keywords, such as
   3503   ;; `(,@dired-font-lock-keywords ,@denote-faces-file-name-keywords).
   3504   ;; None of them could be unset upon disabling `denote-dired-mode'.
   3505   ;; As such, I am using the `when' here.
   3506   (when (derived-mode-p 'dired-mode)
   3507     (font-lock-add-keywords nil denote-faces-file-name-keywords t)))
   3508 
   3509 (defun denote-dired-remove-font-lock (&rest _)
   3510   "Remove `denote-faces-file-name-keywords' from font lock keywords."
   3511   ;; See NOTE in `denote-dired-add-font-lock'.
   3512   (when (derived-mode-p 'dired-mode)
   3513     (font-lock-remove-keywords nil denote-faces-file-name-keywords)))
   3514 
   3515 (declare-function wdired-change-to-wdired-mode "wdired")
   3516 (declare-function wdired-finish-edit "wdired")
   3517 
   3518 ;;;###autoload
   3519 (define-minor-mode denote-dired-mode
   3520   "Fontify all Denote-style file names.
   3521 Add this or `denote-dired-mode-in-directories' to
   3522 `dired-mode-hook'."
   3523   :global nil
   3524   :group 'denote-dired
   3525   (if denote-dired-mode
   3526       (progn
   3527         (denote-dired-add-font-lock)
   3528         (advice-add #'wdired-change-to-wdired-mode :after #'denote-dired-add-font-lock)
   3529         (advice-add #'wdired-finish-edit :after #'denote-dired-add-font-lock))
   3530     (denote-dired-remove-font-lock)
   3531     (advice-remove #'wdired-change-to-wdired-mode #'denote-dired-add-font-lock)
   3532     (advice-remove #'wdired-finish-edit #'denote-dired-add-font-lock))
   3533   (font-lock-flush (point-min) (point-max)))
   3534 
   3535 (defun denote-dired--modes-dirs-as-dirs ()
   3536   "Return `denote-dired-directories' as directories.
   3537 The intent is to basically make sure that however a path is
   3538 written, it is always returned as a directory."
   3539   (mapcar
   3540    (lambda (dir)
   3541      (file-name-as-directory (file-truename dir)))
   3542    denote-dired-directories))
   3543 
   3544 ;;;###autoload
   3545 (defun denote-dired-mode-in-directories ()
   3546   "Enable `denote-dired-mode' in `denote-dired-directories'.
   3547 Add this function to `dired-mode-hook'.
   3548 
   3549 If `denote-dired-directories-include-subdirectories' is non-nil,
   3550 also enable it in all subdirectories."
   3551   (when-let ((dirs (denote-dired--modes-dirs-as-dirs))
   3552              ;; Also include subdirs
   3553              ((or (member (file-truename default-directory) dirs)
   3554                   (and denote-dired-directories-include-subdirectories
   3555                        (seq-some
   3556                         (lambda (dir)
   3557                           (string-prefix-p dir (file-truename default-directory)))
   3558                         dirs)))))
   3559     (denote-dired-mode 1)))
   3560 
   3561 ;;;; The linking facility
   3562 
   3563 (defgroup denote-link ()
   3564   "Link facility for Denote."
   3565   :group 'denote)
   3566 
   3567 ;;;;; User options
   3568 
   3569 (defcustom denote-link-backlinks-display-buffer-action
   3570   '((display-buffer-reuse-window display-buffer-below-selected)
   3571     (window-height . fit-window-to-buffer))
   3572   "The action used to display the current file's backlinks buffer.
   3573 
   3574 The value has the form (FUNCTION . ALIST), where FUNCTION is
   3575 either an \"action function\", a list thereof, or possibly an
   3576 empty list.  ALIST is a list of \"action alist\" which may be
   3577 omitted (or be empty).
   3578 
   3579 Sample configuration to display the buffer in a side window on
   3580 the left of the Emacs frame:
   3581 
   3582     (setq denote-link-backlinks-display-buffer-action
   3583           (quote ((display-buffer-reuse-window
   3584                    display-buffer-in-side-window)
   3585                   (side . left)
   3586                   (slot . 99)
   3587                   (window-width . 0.3))))
   3588 
   3589 See Info node `(elisp) Displaying Buffers' for more details
   3590 and/or the documentation string of `display-buffer'."
   3591   :type '(cons (choice (function :tag "Display Function")
   3592                        (repeat :tag "Display Functions" function))
   3593                alist)
   3594   :package-version '(denote . "0.1.0")
   3595   :group 'denote-link)
   3596 
   3597 ;;;;; Link to note
   3598 
   3599 (defvar denote-org-link-format "[[denote:%s][%s]]"
   3600   "Format of Org link to note.
   3601 The value is passed to `format' with IDENTIFIER and TITLE
   3602 arguments, in this order.
   3603 
   3604 Also see `denote-org-link-in-context-regexp'.")
   3605 
   3606 (defvar denote-md-link-format "[%2$s](denote:%1$s)"
   3607   "Format of Markdown link to note.
   3608 The %N$s notation used in the default value is for `format' as
   3609 the supplied arguments are IDENTIFIER and TITLE, in this order.
   3610 
   3611 Also see `denote-md-link-in-context-regexp'.")
   3612 
   3613 (defvar denote-id-only-link-format "[[denote:%s]]"
   3614   "Format of identifier-only link to note.
   3615 The value is passed to `format' with IDENTIFIER as its sole
   3616 argument.
   3617 
   3618 Also see `denote-id-only-link-in-context-regexp'.")
   3619 
   3620 (defvar denote-org-link-in-context-regexp
   3621   (concat "\\[\\[" "denote:"  "\\(?1:" denote-id-regexp "\\)" "]" "\\[.*?]]")
   3622   "Regexp to match an Org link in its context.
   3623 The format of such links is `denote-org-link-format'.")
   3624 
   3625 (defvar denote-md-link-in-context-regexp
   3626   (concat "\\[.*?]" "(denote:"  "\\(?1:" denote-id-regexp "\\)" ")")
   3627   "Regexp to match a Markdown link in its context.
   3628 The format of such links is `denote-md-link-format'.")
   3629 
   3630 (defvar denote-id-only-link-in-context-regexp
   3631   (concat "\\[\\[" "denote:"  "\\(?1:" denote-id-regexp "\\)" "]]")
   3632   "Regexp to match an identifier-only link in its context.
   3633 The format of such links is `denote-id-only-link-format'."  )
   3634 
   3635 (defun denote-format-link (file description file-type id-only)
   3636   "Prepare link to FILE using DESCRIPTION.
   3637 
   3638 FILE-TYPE and ID-ONLY are used to get the format of the link.
   3639 See the `:link' property of `denote-file-types'."
   3640   (format
   3641    (if (or id-only (null description) (string-empty-p description))
   3642        denote-id-only-link-format
   3643      (denote--link-format file-type))
   3644    (denote-retrieve-filename-identifier file)
   3645    description))
   3646 
   3647 (make-obsolete 'denote-link--format-link 'denote-format-link "2.1.0")
   3648 (make-obsolete 'denote-link-signature-format nil "2.3.0")
   3649 
   3650 (defun denote--link-get-description (file)
   3651   "Return link description for FILE."
   3652   (funcall
   3653    (or denote-link-description-function #'denote-link-description-with-signature-and-title)
   3654    file))
   3655 
   3656 (defun denote-link-description-with-signature-and-title (file)
   3657   "Return link description for FILE.
   3658 
   3659 - If the region is active, use it as the description.
   3660 
   3661 - If FILE as a signature, then format the description as a sequence of
   3662   the signature text and the title with two spaces between them.
   3663 
   3664 - If FILE does not have a signature, then use its title as the
   3665   description.
   3666 
   3667 This is useful as the value of the user option
   3668 `denote-link-description-function'."
   3669   (let* ((file-type (denote-filetype-heuristics file))
   3670          (signature (denote-retrieve-filename-signature file))
   3671          (title (denote-retrieve-title-or-filename file file-type))
   3672          (region-text (denote--get-active-region-content)))
   3673     (cond
   3674      (region-text region-text)
   3675      ((and signature title) (format "%s  %s" signature title))
   3676      (t title))))
   3677 
   3678 (defun denote--get-active-region-content ()
   3679   "Return the text of the active region, else nil."
   3680   (when-let (((region-active-p))
   3681              (beg (region-beginning))
   3682              (end (region-end)))
   3683     (string-trim (buffer-substring-no-properties beg end))))
   3684 
   3685 (defun denote--delete-active-region-content ()
   3686   "Delete the content of the active region, if any."
   3687   (when-let (((region-active-p))
   3688              (beg (region-beginning))
   3689              (end (region-end)))
   3690     (delete-region beg end)))
   3691 
   3692 ;;;###autoload
   3693 (defun denote-link (file file-type description &optional id-only)
   3694   "Create link to FILE note in variable `denote-directory' with DESCRIPTION.
   3695 
   3696 When called interactively, prompt for FILE using completion.  In
   3697 this case, derive FILE-TYPE from the current buffer.
   3698 
   3699 The DESCRIPTION is returned by the function specified in variable
   3700 `denote-link-description-function'.  If the region is active, its
   3701 content is deleted and can be used as the description of the
   3702 link.  The default value of `denote-link-description-function'
   3703 returns the content of the active region, if any, else the title
   3704 of the linked file is used as the description.  The title comes
   3705 either from the front matter or the file name.  Note that if you
   3706 change the default value of `denote-link-description-function',
   3707 make sure to use the `region-text' parameter.  Regardless of the
   3708 value of this user option, `denote-link' will always replace the
   3709 content of the active region.
   3710 
   3711 With optional ID-ONLY as a non-nil argument, such as with a
   3712 universal prefix (\\[universal-argument]), insert links with just
   3713 the identifier and no further description.  In this case, the
   3714 link format is always [[denote:IDENTIFIER]].  If the DESCRIPTION
   3715 is empty, the link is also as if ID-ONLY were non-nil.  The
   3716 default value of `denote-link-description-function' returns an
   3717 empty string when the region is empty.  Thus, the link will have
   3718 no description in this case.
   3719 
   3720 When called from Lisp, FILE is a string representing a full file
   3721 system path.  FILE-TYPE is a symbol as described in
   3722 `denote-file-type'.  DESCRIPTION is a string.  Whether the caller
   3723 treats the active region specially, is up to it."
   3724   (interactive
   3725    (let* ((file (denote-file-prompt nil "Link to FILE"))
   3726           (file-type (denote-filetype-heuristics buffer-file-name))
   3727           (description (when (file-exists-p file)
   3728                          (denote--link-get-description file))))
   3729        (list file file-type description current-prefix-arg)))
   3730   (unless (or (denote--file-type-org-capture-p)
   3731               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   3732     (user-error "The current file type is not recognized by Denote"))
   3733   (unless (file-exists-p file)
   3734     (user-error "The linked file does not exist"))
   3735   (let* ((beg (point)))
   3736     (denote--delete-active-region-content)
   3737     (insert (denote-format-link file description file-type id-only))
   3738     (unless (derived-mode-p 'org-mode)
   3739       (make-button beg (point) 'type 'denote-link-button))))
   3740 
   3741 (define-obsolete-function-alias
   3742   'denote-link-insert-link
   3743   'denote-insert-link
   3744   "2.0.0")
   3745 
   3746 (defalias 'denote-insert-link 'denote-link
   3747   "Alias for `denote-link' command.")
   3748 
   3749 ;;;###autoload
   3750 (defun denote-link-with-signature ()
   3751   "Insert link to file with signature.
   3752 Prompt for file using minibuffer completion, limiting the list of
   3753 candidates to files with a signature in their file name.
   3754 
   3755 By default, the description of the link includes the signature,
   3756 if present, followed by the file's title, if any.
   3757 
   3758 For more advanced uses with Lisp, refer to the `denote-link'
   3759 function."
   3760   (declare (interactive-only t))
   3761   (interactive)
   3762   (unless (or (denote--file-type-org-capture-p)
   3763               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   3764     (user-error "The current file type is not recognized by Denote"))
   3765   (let* ((file (denote-file-prompt "="))
   3766          (type (denote-filetype-heuristics (buffer-file-name)))
   3767          (description (denote--link-get-description file)))
   3768     (denote-link file type description)))
   3769 
   3770 (defun denote-link--collect-identifiers (regexp)
   3771   "Return collection of identifiers in buffer matching REGEXP."
   3772   (let (matches)
   3773     (save-excursion
   3774       (goto-char (point-min))
   3775       (while (or (re-search-forward regexp nil t)
   3776                  (re-search-forward denote-id-only-link-in-context-regexp nil t))
   3777         (push (match-string-no-properties 1) matches)))
   3778     matches))
   3779 
   3780 (defun denote-link--expand-identifiers (regexp)
   3781   "Expend identifiers matching REGEXP into file paths."
   3782   (let ((files (denote-directory-files))
   3783         found-files)
   3784     (dolist (file files)
   3785       (dolist (i (denote-link--collect-identifiers regexp))
   3786         (when (string-prefix-p i (file-name-nondirectory file))
   3787           (push file found-files))))
   3788     found-files))
   3789 
   3790 (defvar denote-link-find-file-history nil
   3791   "History for `denote-find-link'.")
   3792 
   3793 (defalias 'denote-link--find-file-history 'denote-link-find-file-history
   3794   "Compatibility alias for `denote-link-find-file-history'.")
   3795 
   3796 (defun denote-link--find-file-prompt (files)
   3797   "Prompt for linked file among FILES."
   3798   (let ((file-names (mapcar #'denote-get-file-name-relative-to-denote-directory
   3799                             files)))
   3800     (completing-read
   3801      "Find linked file: "
   3802      (denote--completion-table 'file file-names)
   3803      nil t nil 'denote-link-find-file-history)))
   3804 
   3805 (defun denote-link-return-links (&optional file)
   3806   "Return list of links in current or optional FILE.
   3807 Also see `denote-link-return-backlinks'."
   3808   (when-let ((current-file (or file (buffer-file-name)))
   3809              ((denote-file-has-supported-extension-p current-file))
   3810              (file-type (denote-filetype-heuristics current-file))
   3811              (regexp (denote--link-in-context-regexp file-type)))
   3812     (with-temp-buffer
   3813       (insert-file-contents current-file)
   3814       (denote-link--expand-identifiers regexp))))
   3815 
   3816 (defalias 'denote-link-return-forelinks 'denote-link-return-links
   3817   "Alias for `denote-link-return-links'.")
   3818 
   3819 (define-obsolete-function-alias
   3820   'denote-link-find-file
   3821   'denote-find-link
   3822   "2.0.0")
   3823 
   3824 ;;;###autoload
   3825 (defun denote-find-link ()
   3826   "Use minibuffer completion to visit linked file."
   3827   (declare (interactive-only t))
   3828   (interactive)
   3829   (find-file
   3830    (concat
   3831     (denote-directory)
   3832     (denote-link--find-file-prompt
   3833      (or (denote-link-return-links)
   3834          (user-error "No links found"))))))
   3835 
   3836 (defun denote-link-return-backlinks (&optional file)
   3837   "Return list of backlinks in current or optional FILE.
   3838 Also see `denote-link-return-links'."
   3839   (when-let ((current-file (or file (buffer-file-name)))
   3840              (id (denote-retrieve-filename-identifier-with-error current-file)))
   3841     (delete current-file (denote--retrieve-files-in-xrefs id))))
   3842 
   3843 (define-obsolete-function-alias
   3844   'denote-link-find-backlink
   3845   'denote-find-backlink
   3846   "2.0.0")
   3847 
   3848 ;;;###autoload
   3849 (defun denote-find-backlink ()
   3850   "Use minibuffer completion to visit backlink to current file.
   3851 
   3852 Like `denote-find-link', but select backlink to follow."
   3853   (declare (interactive-only t))
   3854   (interactive)
   3855   (find-file
   3856    (denote-get-path-by-id
   3857     (denote-extract-id-from-string
   3858      (denote-link--find-file-prompt
   3859       (or (denote-link-return-backlinks)
   3860           (user-error "No backlinks found")))))))
   3861 
   3862 ;;;###autoload
   3863 (defun denote-link-after-creating (&optional id-only)
   3864   "Create new note in the background and link to it directly.
   3865 
   3866 Use `denote' interactively to produce the new note.  Its doc
   3867 string explains which prompts will be used and under what
   3868 conditions.
   3869 
   3870 With optional ID-ONLY as a prefix argument create a link that
   3871 consists of just the identifier.  Else try to also include the
   3872 file's title.  This has the same meaning as in `denote-link'.
   3873 
   3874 For a variant of this, see `denote-link-after-creating-with-command'.
   3875 
   3876 IMPORTANT NOTE: Normally, `denote' does not save the buffer it
   3877 produces for the new note.  This is a safety precaution to not
   3878 write to disk unless the user wants it (e.g. the user may choose
   3879 to kill the buffer, thus cancelling the creation of the note).
   3880 However, for this command the creation of the note happens in the
   3881 background and the user may miss the step of saving their buffer.
   3882 We thus have to save the buffer in order to (i) establish valid
   3883 links, and (ii) retrieve whatever front matter from the target
   3884 file.  Though see `denote-save-buffer-after-creation'."
   3885   (interactive "P")
   3886   (unless (or (denote--file-type-org-capture-p)
   3887               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   3888     (user-error "The current file type is not recognized by Denote"))
   3889   (let* ((type (denote-filetype-heuristics (buffer-file-name)))
   3890          (path (denote--command-with-features #'denote nil nil :save :in-background))
   3891          (description (denote--link-get-description path)))
   3892     (denote-link path type description id-only)))
   3893 
   3894 ;;;###autoload
   3895 (defun denote-link-after-creating-with-command (command &optional id-only)
   3896   "Like `denote-link-after-creating' but prompt for note-making COMMAND.
   3897 Use this to, for example, call `denote-signature' so that the
   3898 newly created note has a signature as part of its file name.
   3899 
   3900 Optional ID-ONLY has the same meaning as in the command
   3901 `denote-link-after-creating'."
   3902   (interactive
   3903    (list
   3904     (denote-command-prompt)
   3905     current-prefix-arg))
   3906   (unless (or (denote--file-type-org-capture-p)
   3907               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   3908     (user-error "The current file type is not recognized by Denote"))
   3909   (let* ((type (denote-filetype-heuristics (buffer-file-name)))
   3910          (path (denote--command-with-features command nil nil :save :in-background))
   3911          (description (denote--link-get-description path)))
   3912     (denote-link path type description id-only)))
   3913 
   3914 ;;;###autoload
   3915 (defun denote-link-or-create (target &optional id-only)
   3916   "Use `denote-link' on TARGET file, creating it if necessary.
   3917 
   3918 If TARGET file does not exist, call `denote-link-after-creating'
   3919 which runs the `denote' command interactively to create the file.
   3920 The established link will then be targeting that new file.
   3921 
   3922 If TARGET file does not exist, add the user input that was used
   3923 to search for it to the minibuffer history of the
   3924 `denote-file-prompt'.  The user can then retrieve and possibly
   3925 further edit their last input, using it as the newly created
   3926 note's actual title.  At the `denote-file-prompt' type
   3927 \\<minibuffer-local-map>\\[previous-history-element].
   3928 
   3929 With optional ID-ONLY as a prefix argument create a link that
   3930 consists of just the identifier.  Else try to also include the
   3931 file's title.  This has the same meaning as in `denote-link'."
   3932   (interactive
   3933    (let* ((target (denote-file-prompt)))
   3934      (unless (and target (file-exists-p target))
   3935        (setq target (denote--command-with-features #'denote :use-file-prompt-as-def-title :ignore-region :save :in-background)))
   3936      (list target current-prefix-arg)))
   3937   (unless (or (denote--file-type-org-capture-p)
   3938               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   3939     (user-error "The current file type is not recognized by Denote"))
   3940   (denote-link target
   3941                (denote-filetype-heuristics (buffer-file-name))
   3942                (denote--link-get-description target)
   3943                id-only))
   3944 
   3945 (defalias 'denote-link-to-existing-or-new-note 'denote-link-or-create
   3946   "Alias for `denote-link-or-create' command.")
   3947 
   3948 ;;;;; Link buttons
   3949 
   3950 ;; Evaluate: (info "(elisp) Button Properties")
   3951 ;;
   3952 ;; Button can provide a help-echo function as well, but I think we might
   3953 ;; not need it.
   3954 (define-button-type 'denote-link-button
   3955   'follow-link t
   3956   'face 'denote-faces-link
   3957   'action #'denote-link--find-file-at-button)
   3958 
   3959 (autoload 'thing-at-point-looking-at "thingatpt")
   3960 
   3961 (defun denote-link--link-at-point-string ()
   3962   "Return identifier at point."
   3963   (when (or (thing-at-point-looking-at denote-id-only-link-in-context-regexp)
   3964             (thing-at-point-looking-at denote-md-link-in-context-regexp)
   3965             (thing-at-point-looking-at denote-org-link-in-context-regexp)
   3966             ;; Meant to handle the case where a link is broken by
   3967             ;; `fill-paragraph' into two lines, in which case it
   3968             ;; buttonizes only the "denote:ID" part.  Example:
   3969             ;;
   3970             ;; [[denote:20220619T175212][This is a
   3971             ;; test]]
   3972             ;;
   3973             ;; Maybe there is a better way?
   3974             (thing-at-point-looking-at "\\[\\(denote:.*\\)]"))
   3975     (match-string-no-properties 0)))
   3976 
   3977 ;; NOTE 2022-06-15: I add this as a variable for advanced users who may
   3978 ;; prefer something else.  If there is demand for it, we can make it a
   3979 ;; defcustom, but I think it would be premature at this stage.
   3980 (defvar denote-link-button-action #'find-file-other-window
   3981   "Display buffer action for Denote buttons.")
   3982 
   3983 (defun denote-link--find-file-at-button (button)
   3984   "Visit file referenced by BUTTON."
   3985   (let* ((id (denote-extract-id-from-string
   3986               (buffer-substring-no-properties
   3987                (button-start button)
   3988                (button-end button))))
   3989          (file (denote-get-path-by-id id)))
   3990     (funcall denote-link-button-action file)))
   3991 
   3992 ;;;###autoload
   3993 (defun denote-link-buttonize-buffer (&optional beg end)
   3994   "Make denote: links actionable buttons in the current buffer.
   3995 
   3996 Buttonization applies to the plain text and Markdown file types,
   3997 per the user option `denote-file-types'.  It will not do anything
   3998 in `org-mode' buffers, as buttons already work there.  If you do
   3999 not use Markdown or plain text, then you do not need this.
   4000 
   4001 Links work when they point to a file inside the variable
   4002 `denote-directory'.
   4003 
   4004 To buttonize links automatically add this function to the
   4005 `find-file-hook'.  Or call it interactively for on-demand
   4006 buttonization.
   4007 
   4008 When called from Lisp, with optional BEG and END as buffer
   4009 positions, limit the process to the region in-between."
   4010   (interactive)
   4011   (when (and (not (derived-mode-p 'org-mode))
   4012              buffer-file-name
   4013              (denote-file-has-identifier-p buffer-file-name))
   4014     (save-excursion
   4015       (goto-char (or beg (point-min)))
   4016       (while (re-search-forward denote-id-regexp end t)
   4017         (when-let ((string (denote-link--link-at-point-string))
   4018                    (beg (match-beginning 0))
   4019                    (end (match-end 0)))
   4020           (make-button beg end 'type 'denote-link-button))))))
   4021 
   4022 (defun denote-link-markdown-follow (link)
   4023   "Function to open Denote file present in LINK.
   4024 To be assigned to `markdown-follow-link-functions'."
   4025   (when (ignore-errors (string-match denote-id-regexp link))
   4026     (funcall denote-link-button-action
   4027              (denote-get-path-by-id (match-string 0 link)))))
   4028 
   4029 (eval-after-load 'markdown-mode
   4030   '(add-hook 'markdown-follow-link-functions #'denote-link-markdown-follow))
   4031 
   4032 ;;;;; Backlinks' buffer
   4033 
   4034 (define-button-type 'denote-link-backlink-button
   4035   'follow-link t
   4036   'action #'denote-link--backlink-find-file
   4037   'face nil)            ; we use this face though we style it later
   4038 
   4039 (defun denote-link--backlink-find-file (button)
   4040   "Action for BUTTON to `find-file'."
   4041   (funcall denote-link-button-action (buffer-substring (button-start button) (button-end button))))
   4042 
   4043 (defun denote-link--display-buffer (buf)
   4044   "Run `display-buffer' on BUF.
   4045 Expand `denote-link-backlinks-display-buffer-action'."
   4046   (display-buffer
   4047    buf
   4048    `(,@denote-link-backlinks-display-buffer-action)))
   4049 
   4050 (define-obsolete-function-alias
   4051   'denote-backlinks-next
   4052   'denote-backlinks-mode-next
   4053   "2.3.0")
   4054 
   4055 (defun denote-backlinks-mode-next (n)
   4056   "Use appropriate command for forward motion in backlinks buffer.
   4057 With N as a numeric argument, move to the Nth button from point.
   4058 A nil value of N is understood as 1.
   4059 
   4060 When `denote-backlinks-show-context' is nil, move between files
   4061 in the backlinks buffer.
   4062 
   4063 When `denote-backlinks-show-context' is non-nil move between
   4064 matching identifiers."
   4065   (interactive "p" denote-backlinks-mode)
   4066   (unless (derived-mode-p 'denote-backlinks-mode)
   4067     (user-error "Only use this in a Denote backlinks buffer"))
   4068   (if denote-backlinks-show-context
   4069       (xref-next-line)
   4070     (forward-button n)))
   4071 
   4072 (define-obsolete-function-alias
   4073   'denote-backlinks-prev
   4074   'denote-backlinks-mode-previous
   4075   "2.3.0")
   4076 
   4077 (defun denote-backlinks-mode-previous (n)
   4078   "Use appropriate command for backward motion in backlinks buffer.
   4079 With N as a numeric argument, move to the Nth button from point.
   4080 A nil value of N is understood as 1.
   4081 
   4082 When `denote-backlinks-show-context' is nil, move between files
   4083 in the backlinks buffer.
   4084 
   4085 When `denote-backlinks-show-context' is non-nil move between
   4086 matching identifiers."
   4087   (interactive "p" denote-backlinks-mode)
   4088   (unless (derived-mode-p 'denote-backlinks-mode)
   4089     (user-error "Only use this in a Denote backlinks buffer"))
   4090   (if denote-backlinks-show-context
   4091       (xref-prev-line)
   4092     (backward-button n)))
   4093 
   4094 (defvar denote-backlinks-mode-map
   4095   (let ((m (make-sparse-keymap)))
   4096     (define-key m "n" #'denote-backlinks-mode-next)
   4097     (define-key m "p" #'denote-backlinks-mode-previous)
   4098     (define-key m "g" #'revert-buffer)
   4099     m)
   4100   "Keymap for `denote-backlinks-mode'.")
   4101 
   4102 (define-derived-mode denote-backlinks-mode xref--xref-buffer-mode "Backlinks"
   4103   :interactive nil
   4104   "Major mode for backlinks buffers."
   4105   (unless denote-backlinks-show-context
   4106     (font-lock-add-keywords nil denote-faces-file-name-keywords t)))
   4107 
   4108 (defun denote-link--prepare-backlinks (fetcher _alist)
   4109   "Create backlinks' buffer for the current note.
   4110 FETCHER is a function that fetches a list of xrefs.  It is called
   4111 with `funcall' with no argument like `xref--fetcher'.
   4112 
   4113 In the case of `denote', `apply-partially' is used to create a
   4114 function that has already applied another function to multiple
   4115 arguments.
   4116 
   4117 ALIST is not used in favour of using
   4118 `denote-link-backlinks-display-buffer-action'."
   4119   (let* ((inhibit-read-only t)
   4120          (file (buffer-file-name))
   4121          (file-type (denote-filetype-heuristics file))
   4122          (id (denote-retrieve-filename-identifier-with-error file))
   4123          (buf (format "*denote-backlinks to %s*" id))
   4124          ;; We retrieve results in absolute form and change the absolute
   4125          ;; path to a relative path a few lines below. We could add a
   4126          ;; suitable function to project-find-functions and the results
   4127          ;; would be automatically in relative form, but eventually
   4128          ;; notes may not be all under a common directory (or project).
   4129          (xref-file-name-display 'abs)
   4130          (xref-alist (xref--analyze (funcall fetcher)))
   4131          (dir (denote-directory)))
   4132     ;; Change the GROUP of each item in xref-alist to a relative path
   4133     (mapc (lambda (x)
   4134             (setf (car x) (denote-get-file-name-relative-to-denote-directory (car x))))
   4135           xref-alist)
   4136     (with-current-buffer (get-buffer-create buf)
   4137       (setq-local default-directory dir)
   4138       (erase-buffer)
   4139       (setq overlay-arrow-position nil)
   4140       (denote-backlinks-mode)
   4141       (goto-char (point-min))
   4142       (when-let  ((title (denote-retrieve-front-matter-title-value file file-type))
   4143                   (heading (format "Backlinks to %S (%s)" title id))
   4144                   (l (length heading)))
   4145         (insert (format "%s\n%s\n\n" heading (make-string l ?-))))
   4146       (if denote-backlinks-show-context
   4147           (xref--insert-xrefs xref-alist)
   4148         (mapc (lambda (x)
   4149                 (insert (car x))
   4150                 (make-button (line-beginning-position) (line-end-position) :type 'denote-link-backlink-button)
   4151                 (newline))
   4152               xref-alist))
   4153       (goto-char (point-min))
   4154       (setq-local revert-buffer-function
   4155                   (lambda (_ignore-auto _noconfirm)
   4156                     (when-let ((buffer-file-name file))
   4157                       (denote-link--prepare-backlinks
   4158                        (apply-partially #'xref-matches-in-files id
   4159                                         (denote-directory-files nil :omit-current :text-only))
   4160                        nil)))))
   4161     (denote-link--display-buffer buf)))
   4162 
   4163 (define-obsolete-function-alias
   4164   'denote-link-backlinks
   4165   'denote-backlinks
   4166   "2.0.0")
   4167 
   4168 ;;;###autoload
   4169 (defun denote-backlinks ()
   4170   "Produce a buffer with backlinks to the current note.
   4171 
   4172 The backlinks' buffer shows the file name of the note linking to
   4173 the current note, as well as the context of each link.
   4174 
   4175 File names are fontified by Denote if the user option
   4176 `denote-link-fontify-backlinks' is non-nil.  If this user option
   4177 is nil, the buffer is fontified by Xref.
   4178 
   4179 The placement of the backlinks' buffer is controlled by the user
   4180 option `denote-link-backlinks-display-buffer-action'.  By
   4181 default, it will show up below the current window."
   4182   (interactive)
   4183   (let ((file (buffer-file-name)))
   4184     (when (denote-file-is-writable-and-supported-p file)
   4185       (let* ((id (denote-retrieve-filename-identifier-with-error file))
   4186              (xref-show-xrefs-function #'denote-link--prepare-backlinks))
   4187         (xref--show-xrefs
   4188          (apply-partially #'xref-matches-in-files id
   4189                           (denote-directory-files nil :omit-current :text-only))
   4190          nil)))))
   4191 
   4192 (define-obsolete-function-alias
   4193   'denote-link-show-backlinks-buffer
   4194   'denote-show-backlinks-buffer
   4195   "2.0.0")
   4196 
   4197 (defalias 'denote-show-backlinks-buffer 'denote-backlinks
   4198   "Alias for `denote-backlinks' command.")
   4199 
   4200 ;;;;; Add links matching regexp
   4201 
   4202 (defvar denote-link--prepare-links-format "- %s\n"
   4203   "Format specifiers for `denote-link-add-links'.")
   4204 
   4205 ;; NOTE 2022-06-16: There is no need to overwhelm the user with options,
   4206 ;; though I expect someone to want to change the sort order.
   4207 (defvar denote-link-add-links-sort nil
   4208   "When t, add REVERSE to `sort-lines' of `denote-link-add-links'.")
   4209 
   4210 (defun denote-link--prepare-links (files current-file-type id-only &optional no-sort)
   4211   "Prepare links to FILES from CURRENT-FILE-TYPE.
   4212 When ID-ONLY is non-nil, use a generic link format.
   4213 
   4214 With optional NO-SORT do not try to sort the inserted lines.
   4215 Otherwise sort lines while accounting for `denote-link-add-links-sort'."
   4216   (with-temp-buffer
   4217     (mapc
   4218      (lambda (file)
   4219        (let ((description (denote--link-get-description file)))
   4220          (insert
   4221           (format
   4222            denote-link--prepare-links-format
   4223            (denote-format-link file description current-file-type id-only)))))
   4224      files)
   4225     (unless no-sort
   4226       (sort-lines denote-link-add-links-sort (point-min) (point-max)))
   4227     (buffer-string)))
   4228 
   4229 (define-obsolete-function-alias
   4230   'denote-link-add-links
   4231   'denote-add-links
   4232   "2.0.0")
   4233 
   4234 (defun denote-link--insert-links (files current-file-type &optional id-only no-sort)
   4235   "Insert at point a typographic list of links matching FILES.
   4236 
   4237 With CURRENT-FILE-TYPE as a symbol among those specified in
   4238 `denote-file-type' (or the `car' of each element in
   4239 `denote-file-types'), format the link accordingly.  With a nil or
   4240 unknown non-nil value, default to the Org notation.
   4241 
   4242 With ID-ONLY as a non-nil value, produce links that consist only
   4243 of the identifier, thus deviating from CURRENT-FILE-TYPE.
   4244 
   4245 Optional NO-SORT is passed to `denote-link--prepare-links'."
   4246   (insert (denote-link--prepare-links files current-file-type id-only no-sort)))
   4247 
   4248 ;;;###autoload
   4249 (defun denote-add-links (regexp &optional id-only)
   4250   "Insert links to all notes matching REGEXP.
   4251 Use this command to reference multiple files at once.
   4252 Particularly useful for the creation of metanotes (read the
   4253 manual for more on the matter).
   4254 
   4255 Optional ID-ONLY has the same meaning as in `denote-link': it
   4256 inserts links with just the identifier."
   4257   (interactive
   4258    (list
   4259     (denote-files-matching-regexp-prompt "Insert links matching REGEXP")
   4260     current-prefix-arg))
   4261   (unless (or (denote--file-type-org-capture-p)
   4262               (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name)))
   4263     (user-error "The current file type is not recognized by Denote"))
   4264   (let ((file-type (denote-filetype-heuristics (buffer-file-name))))
   4265     (if-let ((files (denote-directory-files regexp :omit-current))
   4266              (beg (point)))
   4267         (progn
   4268           (denote-link--insert-links files file-type id-only)
   4269           (denote-link-buttonize-buffer beg (point)))
   4270       (message "No links matching `%s'" regexp))))
   4271 
   4272 (defalias 'denote-link-insert-links-matching-regexp 'denote-add-links
   4273   "Alias for `denote-add-links' command.")
   4274 
   4275 (define-obsolete-function-alias
   4276   'denote-link-add-missing-links
   4277   'denote-add-missing-links
   4278   "2.0.0")
   4279 
   4280 (make-obsolete 'denote-add-missing-links nil "2.2.0")
   4281 
   4282 ;;;;; Links from Dired marks
   4283 
   4284 ;; NOTE 2022-07-21: I don't think we need a history for this one.
   4285 (defun denote-link--buffer-prompt (buffers)
   4286   "Select buffer from BUFFERS visiting Denote notes."
   4287   (let ((buffer-file-names (mapcar #'file-name-nondirectory
   4288                                    buffers)))
   4289     (completing-read
   4290      "Select note buffer: "
   4291      (denote--completion-table 'buffer buffer-file-names)
   4292      nil t)))
   4293 
   4294 (defun denote-link--map-over-notes ()
   4295   "Return list of `denote-file-is-note-p' from Dired marked items."
   4296   (when (denote--dir-in-denote-directory-p default-directory)
   4297     (seq-filter #'denote-file-is-note-p (dired-get-marked-files))))
   4298 
   4299 ;;;###autoload
   4300 (defun denote-link-dired-marked-notes (files buffer &optional id-only)
   4301   "Insert Dired marked FILES as links in BUFFER.
   4302 
   4303 FILES are Denote notes, meaning that they have our file-naming
   4304 scheme, are writable/regular files, and use the appropriate file
   4305 type extension (per `denote-file-type').  Furthermore, the marked
   4306 files need to be inside the variable `denote-directory' or one of
   4307 its subdirectories.  No other file is recognised (the list of
   4308 marked files ignores whatever does not count as a note for our
   4309 purposes).
   4310 
   4311 The BUFFER is one which visits a Denote note file.  If there are
   4312 multiple buffers, prompt with completion for one among them.  If
   4313 there isn't one, throw an error.
   4314 
   4315 With optional ID-ONLY as a prefix argument, insert links with
   4316 just the identifier (same principle as with `denote-link').
   4317 
   4318 This command is meant to be used from a Dired buffer."
   4319   (interactive
   4320    (list
   4321     (denote-link--map-over-notes)
   4322     (let ((file-names (denote--buffer-file-names)))
   4323       (find-file
   4324        (cond
   4325         ((null file-names)
   4326          (user-error "No buffers visiting Denote notes"))
   4327         ((eq (length file-names) 1)
   4328          (car file-names))
   4329         (t
   4330          (denote-link--buffer-prompt file-names)))))
   4331     current-prefix-arg)
   4332    dired-mode)
   4333   (if (null files)
   4334       (user-error "No note files to link to")
   4335     (when (y-or-n-p (format "Create links at point in %s?" buffer))
   4336       (with-current-buffer buffer
   4337         (insert (denote-link--prepare-links
   4338                  files
   4339                  (denote-filetype-heuristics (buffer-file-name))
   4340                  id-only))
   4341         (denote-link-buttonize-buffer)))))
   4342 
   4343 ;;;;; Define menu
   4344 
   4345 (defvar denote--menu-contents
   4346   '("Denote"
   4347     ["Create a note" denote
   4348      :help "Create a new note in the `denote-directory'"]
   4349     ["Create a note with given file type" denote-type
   4350      :help "Create a new note with a given file type in the `denote-directory'"]
   4351     ["Create a note in subdirectory" denote-subdirectory
   4352      :help "Create a new note in a subdirectory of the `denote-directory'"]
   4353     ["Create a note with date" denote-date
   4354      :help "Create a new note with a given date in the `denote-directory'"]
   4355     ["Create a note with signature" denote-signature
   4356      :help "Create a new note with a given signature in the `denote-directory'"]
   4357     ["Open a note or create it if missing" denote-open-or-create
   4358      :help "Open an existing note in the `denote-directory' or create it if missing"]
   4359     ["Open a note or create it with the chosen command" denote-open-or-create-with-command
   4360      :help "Open an existing note or create it with the chosen command if missing"]
   4361     "---"
   4362     ["Rename a file" denote-rename-file
   4363      :help "Rename file interactively"
   4364      :enable (derived-mode-p 'dired-mode 'text-mode)]
   4365     ["Rename this file using its front matter" denote-rename-file-using-front-matter
   4366      :help "Rename the current file using its front matter as input"
   4367      :enable (derived-mode-p 'text-mode)]
   4368     ["Rename Dired marked files interactively" denote-dired-rename-files
   4369      :help "Rename marked files in Dired by prompting for all file name components"
   4370      :enable (derived-mode-p 'dired-mode)]
   4371     ["Rename Dired marked files with keywords" denote-dired-rename-marked-files-with-keywords
   4372      :help "Rename marked files in Dired by prompting for keywords"
   4373      :enable (derived-mode-p 'dired-mode)]
   4374     ["Rename Dired marked files using their front matter" denote-dired-rename-marked-files-using-front-matter
   4375      :help "Rename marked files in Dired using their front matter as input"
   4376      :enable (derived-mode-p 'dired-mode)]
   4377     "---"
   4378     ["Insert a link" denote-link
   4379      :help "Insert link to a file in the `denote-directory'"
   4380      :enable (derived-mode-p 'text-mode)]
   4381     ["Insert links with regexp" denote-add-links
   4382      :help "Insert links to files matching regexp in the `denote-directory'"
   4383      :enable (derived-mode-p 'text-mode)]
   4384     ["Insert Dired marked files as links" denote-link-dired-marked-notes
   4385      :help "Rename marked files in Dired as links in a Denote buffer"
   4386      :enable (derived-mode-p 'dired-mode)]
   4387     ["Show file backlinks" denote-backlinks
   4388      :help "Insert link to a file in the `denote-directory'"
   4389      :enable (derived-mode-p 'text-mode)]
   4390     ["Link to existing note or newly created one" denote-link-or-create
   4391      :help "Insert a link to an existing file, else create it and link to it"
   4392      :enable (derived-mode-p 'text-mode)]
   4393     ["Link to existing note or newly created one with the chosen command" denote-link-or-create-with-command
   4394      :help "Insert a link to an existing file, else create it with the given command and link to it"
   4395      :enable (derived-mode-p 'text-mode)]
   4396     ["Create note in the background and link to it directly" denote-link-after-creating
   4397      :help "Create new note and link to it from the current file"
   4398      :enable (derived-mode-p 'text-mode)]
   4399     ["Create note in the background with chosen command and link to it directly" denote-link-after-creating-with-command
   4400      :help "Create new note with the chosen command and link to it from the current file"
   4401      :enable (derived-mode-p 'text-mode)]
   4402     "---"
   4403     ["Highlight Dired file names" denote-dired-mode
   4404      :help "Apply colors to Denote file name components in Dired"
   4405      :enable (derived-mode-p 'dired-mode)
   4406      :style toggle
   4407      :selected (bound-and-true-p denote-dired-mode)])
   4408   "Contents of the Denote menu.")
   4409 
   4410 (defun denote--menu-bar-enable ()
   4411   "Enable Denote menu bar."
   4412   (define-key-after global-map [menu-bar denote]
   4413     (easy-menu-binding
   4414      (easy-menu-create-menu "Denote" denote--menu-contents) "Denote")
   4415     "Tools"))
   4416 
   4417 ;; Enable Denote menu bar by default
   4418 (denote--menu-bar-enable)
   4419 
   4420 ;;;###autoload
   4421 (define-minor-mode denote-menu-bar-mode "Show Denote menu bar."
   4422   :global t
   4423   :init-value t
   4424   (if denote-menu-bar-mode
   4425       (denote--menu-bar-enable)
   4426     (define-key global-map [menu-bar denote] nil)))
   4427 
   4428 (defun denote-context-menu (menu _click)
   4429   "Populate MENU with Denote commands at CLICK."
   4430   (define-key menu [denote-separator] menu-bar-separator)
   4431   (let ((easy-menu (make-sparse-keymap "Denote")))
   4432     (easy-menu-define nil easy-menu nil
   4433       denote--menu-contents)
   4434     (dolist (item (reverse (lookup-key easy-menu [menu-bar])))
   4435       (when (consp item)
   4436         (define-key menu (vector (car item)) (cdr item)))))
   4437   menu)
   4438 
   4439 ;;;;; Register `denote:' custom Org hyperlink
   4440 
   4441 (declare-function org-link-open-as-file "ol" (path arg))
   4442 
   4443 (defun denote-link--ol-resolve-link-to-target (link &optional full-data)
   4444   "Resolve LINK to target file, with or without additioanl search terms.
   4445 With optional FULL-DATA return a list in the form of (path id search)."
   4446   (let* ((search (and (string-match "::\\(.*\\)\\'" link)
   4447                       (match-string 1 link)))
   4448          (id (if (and search (not (string-empty-p search)))
   4449                  (substring link 0 (match-beginning 0))
   4450                link))
   4451          (path (denote-get-path-by-id id)))
   4452     (cond
   4453      (full-data
   4454       (list path id search))
   4455      ((and search (not (string-empty-p search)))
   4456       (concat path "::" search))
   4457      (path))))
   4458 
   4459 ;;;###autoload
   4460 (defun denote-link-ol-follow (link)
   4461   "Find file of type `denote:' matching LINK.
   4462 LINK is the identifier of the note, optionally followed by a
   4463 search option akin to that of standard Org `file:' link types.
   4464 Read Info node `(org) Search Options'.
   4465 
   4466 Uses the function `denote-directory' to establish the path to the
   4467 file."
   4468   (org-link-open-as-file
   4469    (denote-link--ol-resolve-link-to-target link)
   4470    nil))
   4471 
   4472 ;;;###autoload
   4473 (defun denote-link-ol-complete ()
   4474   "Like `denote-link' but for Org integration.
   4475 This lets the user complete a link through the `org-insert-link'
   4476 interface by first selecting the `denote:' hyperlink type."
   4477   (if-let ((file (denote-file-prompt)))
   4478       (concat "denote:" (denote-retrieve-filename-identifier file))
   4479     (user-error "No files in `denote-directory'")))
   4480 
   4481 (declare-function org-link-store-props "ol.el" (&rest plist))
   4482 (defvar org-store-link-plist)
   4483 
   4484 (declare-function org-entry-put "org" (pom property value))
   4485 (declare-function org-entry-get "org" (pom property &optional inherit literal-nil))
   4486 (declare-function org-id-new "org-id" (&optional prefix))
   4487 
   4488 (defun denote-link-ol-get-id ()
   4489   "Get the CUSTOM_ID of the current entry.
   4490 If the entry already has a CUSTOM_ID, return it as-is, else
   4491 create a new one."
   4492   (let* ((pos (point))
   4493          (id (org-entry-get pos "CUSTOM_ID")))
   4494     (if (and id (stringp id) (string-match-p "\\S-" id))
   4495         id
   4496       (setq id (org-id-new "h"))
   4497       (org-entry-put pos "CUSTOM_ID" id)
   4498       id)))
   4499 
   4500 (declare-function org-get-heading "org" (no-tags no-todo no-priority no-comment))
   4501 
   4502 (defun denote-link-ol-get-heading ()
   4503   "Get current Org heading text."
   4504   (org-get-heading :no-tags :no-todo :no-priority :no-comment))
   4505 
   4506 (defun denote-link-format-heading-description (file-text heading-text)
   4507   "Return description for FILE-TEXT with HEADING-TEXT at the end."
   4508   (format "%s::%s" file-text heading-text))
   4509 
   4510 ;;;###autoload
   4511 (defun denote-link-ol-store ()
   4512   "Handler for `org-store-link' adding support for denote: links.
   4513 Also see the user option `denote-org-store-link-to-heading'."
   4514   (when-let ((file (buffer-file-name))
   4515              ((denote-file-is-note-p file))
   4516              (file-id (denote-retrieve-filename-identifier file))
   4517              (description (denote--link-get-description file)))
   4518     (let ((heading-links (and denote-org-store-link-to-heading (derived-mode-p 'org-mode))))
   4519       (org-link-store-props
   4520        :type "denote"
   4521        :description (if heading-links
   4522                         (denote-link-format-heading-description
   4523                          description
   4524                          (denote-link-ol-get-heading))
   4525                       description)
   4526        :link (if heading-links
   4527                  (format "denote:%s::#%s" file-id (denote-link-ol-get-id))
   4528                (concat "denote:" file-id)))
   4529       org-store-link-plist)))
   4530 
   4531 ;;;###autoload
   4532 (defun denote-link-ol-export (link description format)
   4533   "Export a `denote:' link from Org files.
   4534 The LINK, DESCRIPTION, and FORMAT are handled by the export
   4535 backend."
   4536   (let* ((path-id (denote-link--ol-resolve-link-to-target link :full-data))
   4537          (path (file-relative-name (nth 0 path-id)))
   4538          (id (nth 1 path-id))
   4539          (search (nth 2 path-id))
   4540          (anchor (file-name-sans-extension path))
   4541          (desc (cond
   4542                 (description)
   4543                 (search (format "denote:%s::%s" id search))
   4544                 (t (concat "denote:" id)))))
   4545     (cond
   4546      ((eq format 'html)
   4547       (if search
   4548           (format "<a href=\"%s.html%s\">%s</a>" anchor search desc)
   4549         (format "<a href=\"%s.html\">%s</a>" anchor desc)))
   4550      ((eq format 'latex) (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc))
   4551      ((eq format 'texinfo) (format "@uref{%s,%s}" path desc))
   4552      ((eq format 'ascii) (format "[%s] <denote:%s>" desc path))
   4553      ((eq format 'md) (format "[%s](%s)" desc path))
   4554      (t path))))
   4555 
   4556 ;; The `eval-after-load' part with the quoted lambda is adapted from
   4557 ;; Elfeed: <https://github.com/skeeto/elfeed/>.
   4558 
   4559 ;;;###autoload
   4560 (eval-after-load 'org
   4561   `(funcall
   4562     ;; The extra quote below is necessary because uncompiled closures
   4563     ;; do not evaluate to themselves. The quote is harmless for
   4564     ;; byte-compiled function objects.
   4565     ',(lambda ()
   4566         (with-no-warnings
   4567           (org-link-set-parameters
   4568            "denote"
   4569            :follow #'denote-link-ol-follow
   4570            :face 'denote-faces-link
   4571            :complete #'denote-link-ol-complete
   4572            :store #'denote-link-ol-store
   4573            :export #'denote-link-ol-export)))))
   4574 
   4575 ;;;; Glue code for org-capture
   4576 
   4577 (defgroup denote-org-capture ()
   4578   "Integration between Denote and Org Capture."
   4579   :group 'denote)
   4580 
   4581 (defcustom denote-org-capture-specifiers "%l\n%i\n%?"
   4582   "String with format specifiers for `org-capture-templates'.
   4583 Check that variable's documentation for the details.
   4584 
   4585 The string can include arbitrary text.  It is appended to new
   4586 notes via the `denote-org-capture' function.  Every new note has
   4587 the standard front matter we define."
   4588   :type 'string
   4589   :package-version '(denote . "0.1.0")
   4590   :group 'denote-org-capture)
   4591 
   4592 (defvar denote-last-path nil "Store last path.")
   4593 
   4594 ;;;###autoload
   4595 (defun denote-org-capture ()
   4596   "Create new note through `org-capture-templates'.
   4597 Use this as a function that returns the path to the new file.
   4598 The file is populated with Denote's front matter.  It can then be
   4599 expanded with the usual specifiers or strings that
   4600 `org-capture-templates' supports.
   4601 
   4602 This function obeys `denote-prompts', but it ignores `file-type',
   4603 if present: it always sets the Org file extension for the created
   4604 note to ensure that the capture process works as intended,
   4605 especially for the desired output of the
   4606 `denote-org-capture-specifiers' (which can include arbitrary
   4607 text).
   4608 
   4609 Consult the manual for template samples."
   4610   (let (title keywords subdirectory date template signature)
   4611     (dolist (prompt denote-prompts)
   4612       (pcase prompt
   4613         ('title (setq title (denote-title-prompt
   4614                               (when (use-region-p)
   4615                                 (buffer-substring-no-properties
   4616                                  (region-beginning)
   4617                                  (region-end))))))
   4618         ('keywords (setq keywords (denote-keywords-prompt)))
   4619         ('subdirectory (setq subdirectory (denote-subdirectory-prompt)))
   4620         ('date (setq date (denote-date-prompt)))
   4621         ('template (setq template (denote-template-prompt)))
   4622         ('signature (setq signature (denote-signature-prompt)))))
   4623   (let* ((title (or title ""))
   4624          (date (if (or (null date) (string-empty-p date))
   4625                    (current-time)
   4626                  (denote-valid-date-p date)))
   4627          (id (denote--find-first-unused-id
   4628               (denote-get-identifier date)
   4629               (denote--get-all-used-ids)))
   4630          (keywords (denote-keywords-sort keywords))
   4631          (directory (if (denote--dir-in-denote-directory-p subdirectory)
   4632                         (file-name-as-directory subdirectory)
   4633                       (denote-directory)))
   4634          (template (or (alist-get template denote-templates) ""))
   4635          (signature (or signature ""))
   4636          (front-matter (denote--format-front-matter
   4637                         title (denote--date nil 'org) keywords
   4638                         (denote-get-identifier) 'org)))
   4639     (setq denote-last-path
   4640           (denote--path title keywords directory id 'org signature))
   4641     (denote--keywords-add-to-history keywords)
   4642     (concat front-matter template denote-org-capture-specifiers))))
   4643 
   4644 ;; TODO 2023-12-02: Maybe simplify `denote-org-capture-with-prompts'
   4645 ;; by passing a single PROMPTS that is the same value as `denote-prompts'?
   4646 
   4647 ;;;###autoload
   4648 (defun denote-org-capture-with-prompts (&optional title keywords subdirectory date template)
   4649   "Like `denote-org-capture' but with optional prompt parameters.
   4650 
   4651 When called without arguments, do not prompt for anything.  Just
   4652 return the front matter with title and keyword fields empty and
   4653 the date and identifier fields specified.  Also make the file
   4654 name consist of only the identifier plus the Org file name
   4655 extension.
   4656 
   4657 Otherwise produce a minibuffer prompt for every non-nil value
   4658 that corresponds to the TITLE, KEYWORDS, SUBDIRECTORY, DATE, and
   4659 TEMPLATE arguments.  The prompts are those used by the standard
   4660 `denote' command and all of its utility commands.
   4661 
   4662 When returning the contents that fill in the Org capture
   4663 template, the sequence is as follows: front matter, TEMPLATE, and
   4664 then the value of the user option `denote-org-capture-specifiers'.
   4665 
   4666 Important note: in the case of SUBDIRECTORY actual subdirectories
   4667 must exist---Denote does not create them.  Same principle for
   4668 TEMPLATE as templates must exist and are specified in the user
   4669 option `denote-templates'."
   4670   (let ((denote-prompts '()))
   4671     (when template (push 'template denote-prompts))
   4672     (when date (push 'date denote-prompts))
   4673     (when subdirectory (push 'subdirectory denote-prompts))
   4674     (when keywords (push 'keywords denote-prompts))
   4675     (when title (push 'title denote-prompts))
   4676     (denote-org-capture)))
   4677 
   4678 (defun denote-org-capture-delete-empty-file ()
   4679   "Delete file if capture with `denote-org-capture' is aborted."
   4680   (when-let ((file denote-last-path)
   4681              ((denote--file-empty-p file)))
   4682     (delete-file denote-last-path)))
   4683 
   4684 (add-hook 'org-capture-after-finalize-hook #'denote-org-capture-delete-empty-file)
   4685 
   4686 (provide 'denote)
   4687 ;;; denote.el ends here