dotemacs

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

denote.el (133983B)


      1 ;;; denote.el --- Simple notes with an efficient file-naming scheme -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2022  Free Software Foundation, Inc.
      4 
      5 ;; Author: Protesilaos Stavrou <info@protesilaos.com>
      6 ;; Maintainer: Denote Development <~protesilaos/denote@lists.sr.ht>
      7 ;; URL: https://git.sr.ht/~protesilaos/denote
      8 ;; Mailing-List: https://lists.sr.ht/~protesilaos/denote
      9 ;; Version: 1.2.0
     10 ;; Package-Requires: ((emacs "28.1"))
     11 
     12 ;; This file is NOT part of GNU Emacs.
     13 
     14 ;; This program is free software; you can redistribute it and/or modify
     15 ;; it under the terms of the GNU General Public License as published by
     16 ;; the Free Software Foundation, either version 3 of the License, or
     17 ;; (at your option) any later version.
     18 ;;
     19 ;; This program is distributed in the hope that it will be useful,
     20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     22 ;; GNU General Public License for more details.
     23 ;;
     24 ;; You should have received a copy of the GNU General Public License
     25 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     26 
     27 ;;; Commentary:
     28 ;;
     29 ;; Denote aims to be a simple-to-use, focused-in-scope, and effective
     30 ;; note-taking tool for Emacs.  The manual describes all the
     31 ;; technicalities about the file-naming scheme, points of entry to
     32 ;; creating new notes, commands to check links between notes, and more:
     33 ;; <https://protesilaos.com/emacs/denote>.  If you have the info manual
     34 ;; available, evaluate:
     35 ;;
     36 ;;    (info "(denote) Top")
     37 ;;
     38 ;; What follows is a general overview of its core core design
     39 ;; principles (again: please read the manual for the technicalities):
     40 ;;
     41 ;; * Predictability :: File names must follow a consistent and
     42 ;;   descriptive naming convention (see the manual's "The file-naming
     43 ;;   scheme").  The file name alone should offer a clear indication of
     44 ;;   what the contents are, without reference to any other metadatum.
     45 ;;   This convention is not specific to note-taking, as it is pertinent
     46 ;;   to any form of file that is part of the user's long-term storage
     47 ;;   (see the manual's "Renaming files").
     48 ;;
     49 ;; * Composability :: Be a good Emacs citizen, by integrating with other
     50 ;;   packages or built-in functionality instead of re-inventing
     51 ;;   functions such as for filtering or greping.  The author of Denote
     52 ;;   (Protesilaos, aka "Prot") writes ordinary notes in plain text
     53 ;;   (`.txt'), switching on demand to an Org file only when its expanded
     54 ;;   set of functionality is required for the task at hand (see the
     55 ;;   manual's "Points of entry").
     56 ;;
     57 ;; * Portability :: Notes are plain text and should remain portable.
     58 ;;   The way Denote writes file names, the front matter it includes in
     59 ;;   the note's header, and the links it establishes must all be
     60 ;;   adequately usable with standard Unix tools.  No need for a databse
     61 ;;   or some specialised software.  As Denote develops and this manual
     62 ;;   is fully fleshed out, there will be concrete examples on how to do
     63 ;;   the Denote-equivalent on the command-line.
     64 ;;
     65 ;; * Flexibility :: Do not assume the user's preference for a
     66 ;;   note-taking methodology.  Denote is conceptually similar to the
     67 ;;   Zettelkasten Method, which you can learn more about in this
     68 ;;   detailed introduction: <https://zettelkasten.de/introduction/>.
     69 ;;   Notes are atomic (one file per note) and have a unique identifier.
     70 ;;   However, Denote does not enforce a particular methodology for
     71 ;;   knowledge management, such as a restricted vocabulary or mutually
     72 ;;   exclusive sets of keywords.  Denote also does not check if the user
     73 ;;   writes thematically atomic notes.  It is up to the user to apply
     74 ;;   the requisite rigor and/or creativity in pursuit of their preferred
     75 ;;   workflow (see the manual's "Writing metanotes").
     76 ;;
     77 ;; * Hackability :: Denote's code base consists of small and reusable
     78 ;;   functions.  They all have documentation strings.  The idea is to
     79 ;;   make it easier for users of varying levels of expertise to
     80 ;;   understand what is going on and make surgical interventions where
     81 ;;   necessary (e.g. to tweak some formatting).  In this manual, we
     82 ;;   provide concrete examples on such user-level configurations (see
     83 ;;   the manual's "Keep a journal or diary").
     84 ;;
     85 ;; Now the important part...  "Denote" is the familiar word, though it
     86 ;; also is a play on the "note" concept.  Plus, we can come up with
     87 ;; acronyms, recursive or otherwise, of increasingly dubious utility
     88 ;; like:
     89 ;;
     90 ;; + Don't Ever Note Only The Epiphenomenal
     91 ;; + Denote Everything Neatly; Omit The Excesses
     92 ;;
     93 ;; But we'll let you get back to work.  Don't Eschew or Neglect your
     94 ;; Obligations, Tasks, and Engagements.
     95 
     96 ;;; Code:
     97 
     98 (require 'seq)
     99 (require 'xref)
    100 (require 'dired)
    101 (require 'xdg)
    102 (eval-when-compile (require 'subr-x))
    103 
    104 (defgroup denote ()
    105   "Simple notes with an efficient file-naming scheme."
    106   :group 'files
    107   :link '(info-link "(denote) Top"))
    108 
    109 ;;;; User options
    110 
    111 ;; About the autoload: (info "(elisp) File Local Variables")
    112 
    113 ;;;###autoload (put 'denote-directory 'safe-local-variable (lambda (val) (or (eq val 'local) (eq val 'default-directory))))
    114 (defcustom denote-directory (expand-file-name "notes" (xdg-user-dir "DOCUMENTS"))
    115   "Directory for storing personal notes.
    116 
    117 A safe local value of either `default-directory' or `local' can
    118 be added as a value in a .dir-local.el file.  Do this if you
    119 intend to use multiple directory silos for your notes while still
    120 relying on a global value (which is the value of this variable).
    121 The Denote manual has a sample (search for '.dir-locals.el').
    122 Those silos do not communicate with each other: they remain
    123 separate.
    124 
    125 The local value influences where commands such as `denote' will
    126 place the newly created note.  If the command is called from a
    127 directory or file where the local value exists, then that value
    128 take precedence, otherwise the global value is used.
    129 
    130 If you intend to reference this variable in Lisp, consider using
    131 the function `denote-directory' instead: it returns the path as a
    132 directory and also checks if a safe local value should be used."
    133   :group 'denote
    134   :safe (lambda (val) (or (eq val 'local) (eq val 'default-directory)))
    135   :package-version '(denote . "0.5.0")
    136   :link '(info-link "(denote) Maintain separate directories for notes")
    137   :type 'directory)
    138 
    139 (defcustom denote-known-keywords
    140   '("emacs" "philosophy" "politics" "economics")
    141   "List of strings with predefined keywords for `denote'.
    142 Also see user options: `denote-allow-multi-word-keywords',
    143 `denote-infer-keywords', `denote-sort-keywords'."
    144   :group 'denote
    145   :package-version '(denote . "0.1.0")
    146   :type '(repeat string))
    147 
    148 (defcustom denote-infer-keywords t
    149   "Whether to infer keywords from existing notes' file names.
    150 
    151 When non-nil, search the file names of existing notes in the
    152 variable `denote-directory' for their keyword field and extract
    153 the entries as \"inferred keywords\".  These are combined with
    154 `denote-known-keywords' and are presented as completion
    155 candidates while using `denote' and related commands
    156 interactively.
    157 
    158 If nil, refrain from inferring keywords.  The aforementioned
    159 completion prompt only shows the `denote-known-keywords'.  Use
    160 this if you want to enforce a restricted vocabulary.
    161 
    162 The user option `denote-excluded-keywords-regexp' can be used to
    163 exclude keywords that match a regular expression.
    164 
    165 Inferred keywords are specific to the value of the variable
    166 `denote-directory'.  If a silo with a local value is used, as
    167 explained in that variable's doc string, the inferred keywords
    168 are specific to the given silo.
    169 
    170 For advanced Lisp usage, the function `denote-keywords' returns
    171 the appropriate list of strings."
    172   :group 'denote
    173   :package-version '(denote . "0.1.0")
    174   :type 'boolean)
    175 
    176 (defcustom denote-prompts '(title keywords)
    177   "Specify the prompts of the `denote' command for interactive use.
    178 
    179 The value is a list of symbols, which includes any of the following:
    180 
    181 - `title': Prompt for the title of the new note.
    182 
    183 - `keywords': Prompts with completion for the keywords of the new
    184   note.  Available candidates are those specified in the user
    185   option `denote-known-keywords'.  If the user option
    186   `denote-infer-keywords' is non-nil, keywords in existing note
    187   file names are included in the list of candidates.  The
    188   `keywords' prompt uses `completing-read-multiple', meaning that
    189   it can accept multiple keywords separated by a comma (or
    190   whatever the value of `crm-separator' is).
    191 
    192 - `file-type': Prompts with completion for the file type of the
    193   new note.  Available candidates are those specified in the user
    194   option `denote-file-type'.  Without this prompt, `denote' uses
    195   the value of `denote-file-type'.
    196 
    197 - `subdirectory': Prompts with completion for a subdirectory in
    198   which to create the note.  Available candidates are the value
    199   of the user option `denote-directory' and all of its
    200   subdirectories.  Any subdirectory must already exist: Denote
    201   will not create it.
    202 
    203 - `date': Prompts for the date of the new note.  It will expect
    204   an input like 2022-06-16 or a date plus time: 2022-06-16 14:30.
    205   Without the `date' prompt, the `denote' command uses the
    206   `current-time'.  (To leverage the more sophisticated Org
    207   method, see the `denote-date-prompt-use-org-read-date'.)
    208 
    209 - `template': Prompts for a KEY among `denote-templates'.  The
    210   value of that KEY is used to populate the new note with
    211   content, which is added after the front matter.
    212 
    213 The prompts occur in the given order.
    214 
    215 If the value of this user option is nil, no prompts are used.
    216 The resulting file name will consist of an identifier (i.e. the
    217 date and time) and a supported file type extension (per
    218 `denote-file-type').
    219 
    220 Recall that Denote's standard file-naming scheme is defined as
    221 follows (read the manual for the technicalities):
    222 
    223     DATE--TITLE__KEYWORDS.EXT
    224 
    225 If either or both of the `title' and `keywords' prompts are not
    226 included in the value of this variable, file names will be any of
    227 those permutations:
    228 
    229     DATE.EXT
    230     DATE--TITLE.EXT
    231     DATE__KEYWORDS.EXT
    232 
    233 When in doubt, always include the `title' and `keywords' prompts.
    234 
    235 Finally, this user option only affects the interactive use of the
    236 `denote' command (advanced users can call it from Lisp).  For
    237 ad-hoc interactive actions that do not change the default
    238 behaviour of the `denote' command, users can invoke these
    239 convenience commands: `denote-type', `denote-subdirectory',
    240 `denote-date', `denote-template'."
    241   :group 'denote
    242   :package-version '(denote . "0.5.0")
    243   :link '(info-link "(denote) The denote-prompts option")
    244   :type '(radio (const :tag "Use no prompts" nil)
    245                 (set :tag "Available prompts" :greedy t
    246                      (const :tag "Title" title)
    247                      (const :tag "Keywords" keywords)
    248                      (const :tag "Date" date)
    249                      (const :tag "File type extension" file-type)
    250                      (const :tag "Subdirectory" subdirectory)
    251                      (const :tag "Template" template))))
    252 
    253 (defcustom denote-sort-keywords t
    254   "Whether to sort keywords in new files.
    255 
    256 When non-nil, the keywords of `denote' are sorted with
    257 `string-lessp' regardless of the order they were inserted at the
    258 minibuffer prompt.
    259 
    260 If nil, show the keywords in their given order."
    261   :group 'denote
    262   :package-version '(denote . "0.1.0")
    263   :type 'boolean)
    264 
    265 (defcustom denote-allow-multi-word-keywords t
    266   "If non-nil keywords can consist of multiple words.
    267 Words are automatically separated by a hyphen when using the
    268 `denote' command or related.  The hyphen is the only legal
    269 character---no spaces, no other characters.  If, for example, the
    270 user types <word1_word2> or <word1 word2>, it is converted to
    271 <word1-word2>.
    272 
    273 When nil, do not allow keywords to consist of multiple words.
    274 Reduce them to a single word, such as by turning <word1_word2> or
    275 <word1 word2> into <word1word2>."
    276   :group 'denote
    277   :package-version '(denote . "0.1.0")
    278   :type 'boolean)
    279 
    280 (defcustom denote-file-type nil
    281   "The file type extension for new notes.
    282 
    283 By default (a nil value), the file type is that of Org mode.
    284 Though the `org' symbol can be specified for the same effect.
    285 
    286 When the value is the symbol `markdown-yaml', the file type is
    287 that of Markdown mode and the front matter uses YAML notation.
    288 Similarly, `markdown-toml' is Markdown but has TOML syntax in the
    289 front matter.
    290 
    291 When the value is `text', the file type is that of Text mode.
    292 
    293 Any other non-nil value is the same as the default.
    294 
    295 NOTE: expert users can change the supported file types by leaving
    296 the value of this user option to nil and directly editing the
    297 value of `denote-file-types'.  That variable, which is not a user
    298 option, controls the behaviour of all file-type-aware
    299 functions (creating notes, renaming them, inserting front matter,
    300 formatting a link, etc.).  Consult its documentation for the
    301 technicalities."
    302   :type '(choice
    303           (const :tag "Unspecified (defaults to Org)" nil)
    304           (const :tag "Org mode (default)" org)
    305           (const :tag "Markdown (YAML front matter)" markdown-yaml)
    306           (const :tag "Markdown (TOML front matter)" markdown-toml)
    307           (const :tag "Plain text" text))
    308   :package-version '(denote . "0.6.0")
    309   :group 'denote)
    310 
    311 (defcustom denote-date-format nil
    312   "Date format in the front matter (file header) of new notes.
    313 
    314 When nil (the default value), use a file-type-specific
    315 format (also check `denote-file-type'):
    316 
    317 - For Org, an inactive timestamp is used, such as [2022-06-30 Wed
    318   15:31].
    319 
    320 - For Markdown, the RFC3339 standard is applied:
    321   2022-06-30T15:48:00+03:00.
    322 
    323 - For plain text, the format is that of ISO 8601: 2022-06-30.
    324 
    325 If the value is a string, ignore the above and use it instead.
    326 The string must include format specifiers for the date.  These
    327 are described in the doc string of `format-time-string'."
    328   :type '(choice
    329           (const :tag "Use appropiate format for each file type" nil)
    330           (string :tag "Custom format for `format-time-string'"))
    331   :package-version '(denote . "0.2.0")
    332   :group 'denote)
    333 
    334 (defcustom denote-date-prompt-use-org-read-date nil
    335   "Whether to use `org-read-date' in date prompts.
    336 
    337 If non-nil, use `org-read-date'.  If nil, input the date as a
    338 string, as described in `denote'.
    339 
    340 This option is relevant when `denote-prompts' includes a `date'
    341 and/or when the user invokes the command `denote-date'."
    342   :group 'denote
    343   :package-version '(denote . "0.6.0")
    344   :type 'boolean)
    345 
    346 (defcustom denote-templates nil
    347   "Alist of content templates for new notes.
    348 A template is arbitrary text that Denote will add to a newly
    349 created note right below the front matter.
    350 
    351 Templates are expressed as a (KEY . STRING) association.
    352 
    353 - The KEY is the name which identifies the template.  It is an
    354   arbitrary symbol, such as `report', `memo', `statement'.
    355 
    356 - The STRING is ordinary text that Denote will insert as-is.  It
    357   can contain newline characters to add spacing.  The manual of
    358   Denote contains examples on how to use the `concat' function,
    359   beside writing a generic string.
    360 
    361 The user can choose a template either by invoking the command
    362 `denote-template' or by changing the user option `denote-prompts'
    363 to always prompt for a template when calling the `denote'
    364 command."
    365   :type '(alist :key-type symbol :value-type string)
    366   :package-version '(denote . "0.5.0")
    367   :link '(info-link "(denote) The denote-templates option")
    368   :group 'denote)
    369 
    370 (defcustom denote-backlinks-show-context nil
    371   "When non-nil, show link context in the backlinks buffer.
    372 
    373 The context is the line a link to the current note is found in.
    374 The context includes multiple links to the same note, if those
    375 are present.
    376 
    377 When nil, only show a simple list of file names that link to the
    378 current note."
    379   :group 'denote
    380   :package-version '(denote . "1.2.0")
    381   :type 'boolean)
    382 
    383 (make-obsolete-variable 'denote-link-fontify-backlinks 'denote-backlinks-show-context "1.2.0")
    384 
    385 (defcustom denote-excluded-directories-regexp nil
    386   "Regular expression of directories to exclude from all operations.
    387 Omit matching directories from file prompts and also exclude them
    388 from all functions that check the contents of the variable
    389 `denote-directory'.  The regexp needs to match only the name of
    390 the directory, not its full path.
    391 
    392 File prompts are used by several commands, such as `denote-link'
    393 and `denote-subdirectory'.
    394 
    395 Functions that check for files include `denote-directory-files'
    396 and `denote-directory-subdirectories'.
    397 
    398 The match is performed with `string-match-p'."
    399   :group 'denote
    400   :package-version '(denote . "1.2.0")
    401   :type 'string)
    402 
    403 (defcustom denote-excluded-keywords-regexp nil
    404   "Regular expression of keywords to not infer.
    405 Keywords are inferred from file names and provided at relevant
    406 prompts as completion candidates when the user option
    407 `denote-infer-keywords' is non-nil.
    408 
    409 The match is performed with `string-match-p'."
    410   :group 'denote
    411   :package-version '(denote . "1.2.0")
    412   :type 'string)
    413 
    414 ;;;; Main variables
    415 
    416 ;; For character classes, evaluate: (info "(elisp) Char Classes")
    417 (define-obsolete-variable-alias
    418   'denote--id-format
    419   'denote-id-format
    420   "1.0.0")
    421 
    422 (defconst denote-id-format "%Y%m%dT%H%M%S"
    423   "Format of ID prefix of a note's filename.
    424 The note's ID is derived from the date and time of its creation.")
    425 
    426 (define-obsolete-variable-alias
    427   'denote--id-regexp
    428   'denote-id-regexp
    429   "1.0.0")
    430 
    431 (defconst denote-id-regexp "\\([0-9]\\{8\\}\\)\\(T[0-9]\\{6\\}\\)"
    432   "Regular expression to match `denote-id-format'.")
    433 
    434 (define-obsolete-variable-alias
    435   'denote--title-regexp
    436   'denote-title-regexp
    437   "1.0.0")
    438 
    439 (defconst denote-title-regexp "--\\([[:alnum:][:nonascii:]-]*\\)"
    440   "Regular expression to match the TITLE field in a file name.")
    441 
    442 (define-obsolete-variable-alias
    443   'denote--keywords-regexp
    444   'denote-keywords-regexp
    445   "1.0.0")
    446 
    447 (defconst denote-keywords-regexp "__\\([[:alnum:][:nonascii:]_-]*\\)"
    448   "Regular expression to match the KEYWORDS field in a file name.")
    449 
    450 (define-obsolete-variable-alias
    451   'denote--punctuation-regexp
    452   'denote-excluded-punctuation-regexp
    453   "1.0.0")
    454 
    455 (defconst denote-excluded-punctuation-regexp "[][{}!@#$%^&*()=+'\"?,.\|;:~`‘’“”/]*"
    456   "Punctionation that is removed from file names.
    457 We consider those characters illegal for our purposes.")
    458 
    459 (define-obsolete-variable-alias
    460   'denote-punctuation-excluded-extra-regexp
    461   'denote-excluded-punctuation-extra-regexp
    462   "1.0.0")
    463 
    464 (defvar denote-excluded-punctuation-extra-regexp nil
    465   "Additional punctuation that is removed from file names.
    466 This variable is for advanced users who need to extend the
    467 `denote-excluded-punctuation-regexp'.  Once we have a better
    468 understanding of what we should be omitting, we will update
    469 things accordingly.")
    470 
    471 ;;;; File helper functions
    472 
    473 (defun denote--completion-table (category candidates)
    474   "Pass appropriate metadata CATEGORY to completion CANDIDATES."
    475   (lambda (string pred action)
    476     (if (eq action 'metadata)
    477         `(metadata (category . ,category))
    478       (complete-with-action action candidates string pred))))
    479 
    480 (defun denote-directory ()
    481   "Return path of variable `denote-directory' as a proper directory."
    482   (let* ((val (or (buffer-local-value 'denote-directory (current-buffer))
    483                   denote-directory))
    484          (path (if (or (eq val 'default-directory) (eq val 'local)) default-directory val)))
    485     (unless (file-directory-p path)
    486       (make-directory path t))
    487     (file-name-as-directory (expand-file-name path))))
    488 
    489 (defun denote--slug-no-punct (str)
    490   "Convert STR to a file name slug."
    491   (replace-regexp-in-string
    492    (concat denote-excluded-punctuation-regexp
    493            denote-excluded-punctuation-extra-regexp)
    494    "" str))
    495 
    496 (defun denote--slug-hyphenate (str)
    497   "Replace spaces and underscores with hyphens in STR.
    498 Also replace multiple hyphens with a single one and remove any
    499 leading and trailing hyphen."
    500   (replace-regexp-in-string
    501    "^-\\|-$" ""
    502    (replace-regexp-in-string
    503     "-\\{2,\\}" "-"
    504     (replace-regexp-in-string "_\\|\s+" "-" str))))
    505 
    506 (defun denote-sluggify (str)
    507   "Make STR an appropriate slug for file names and related."
    508   (downcase (denote--slug-hyphenate (denote--slug-no-punct str))))
    509 
    510 (define-obsolete-function-alias
    511   'denote--sluggify
    512   'denote-sluggify
    513   "1.0.0")
    514 
    515 (defun denote-sluggify-and-join (str)
    516   "Sluggify STR while joining separate words."
    517   (downcase
    518    (replace-regexp-in-string
    519     "-" ""
    520     (denote--slug-hyphenate (denote--slug-no-punct str)))))
    521 
    522 (define-obsolete-function-alias
    523   'denote--sluggify-and-join
    524   'denote-sluggify-and-join
    525   "1.0.0")
    526 
    527 (defun denote-sluggify-keywords (keywords)
    528   "Sluggify KEYWORDS, which is a list of strings."
    529   (mapcar (if denote-allow-multi-word-keywords
    530               #'denote-sluggify
    531             #'denote-sluggify-and-join)
    532           keywords))
    533 
    534 (define-obsolete-function-alias
    535   'denote--sluggify-keywords
    536   'denote-sluggify-keywords
    537   "1.0.0")
    538 
    539 (defun denote-desluggify (str)
    540   "Upcase first char in STR and dehyphenate STR, inverting `denote-sluggify'."
    541   (let ((str (replace-regexp-in-string "-" " " str)))
    542     (aset str 0 (upcase (aref str 0)))
    543     str))
    544 
    545 (define-obsolete-function-alias
    546   'denote--desluggify
    547   'denote-desluggify
    548   "1.0.0")
    549 
    550 (defun denote--file-empty-p (file)
    551   "Return non-nil if FILE is empty."
    552   (zerop (or (file-attribute-size (file-attributes file)) 0)))
    553 
    554 (defun denote-file-is-note-p (file)
    555   "Return non-nil if FILE is an actual Denote note.
    556 For our purposes, a note must note be a directory, must satisfy
    557 `file-regular-p', its path must be part of the variable
    558 `denote-directory', it must have a Denote identifier in its name,
    559 and use one of the extensions implied by `denote-file-type'."
    560   (let ((file-name (file-name-nondirectory file)))
    561     (and (not (file-directory-p file))
    562          (file-regular-p file)
    563          (string-prefix-p (denote-directory) (expand-file-name file))
    564          (string-match-p (concat "\\`" denote-id-regexp) file-name)
    565          (denote-file-has-supported-extension-p file))))
    566 
    567 (define-obsolete-function-alias
    568   'denote--only-note-p
    569   'denote-file-is-note-p
    570   "1.0.0")
    571 
    572 (defun denote-file-has-identifier-p (file)
    573   "Return non-nil if FILE has a Denote identifier."
    574   (when file
    575     (string-match-p (concat "\\`" denote-id-regexp)
    576                     (file-name-nondirectory file))))
    577 
    578 (define-obsolete-function-alias
    579   'denote--file-has-identifier-p
    580   'denote-file-has-identifier-p
    581   "1.0.0")
    582 
    583 (defun denote-file-directory-p (file)
    584   "Return non-nil if FILE is a directory.
    585 Omit FILE if it matches the value of user option
    586 `denote-excluded-directories-regexp'."
    587   (and (file-directory-p file)
    588        denote-excluded-directories-regexp
    589        (not (string-match-p denote-excluded-directories-regexp file))))
    590 
    591 (defun denote-file-has-supported-extension-p (file)
    592   "Return non-nil if FILE has supported extension.
    593 Also account for the possibility of an added .gpg suffix.
    594 Supported extensions are those implied by `denote-file-type'."
    595   (let* ((extensions (denote--extensions))
    596          (valid-extensions (append extensions
    597                                    (mapcar (lambda (e)
    598                                              (concat e ".gpg"))
    599                                            extensions))))
    600     (seq-some
    601      (lambda (e) (string-suffix-p e file))
    602      valid-extensions)))
    603 
    604 (define-obsolete-function-alias
    605   'denote--file-supported-extension-p
    606   'denote-file-has-supported-extension-p
    607   "1.0.0")
    608 
    609 (defun denote--file-regular-writable-p (file)
    610   "Return non-nil if FILE is regular and writable."
    611   (and (file-regular-p file)
    612        (file-writable-p file)))
    613 
    614 (defun denote-file-is-writable-and-supported-p (file)
    615   "Return non-nil if FILE is writable and has supported extension."
    616   (and (denote--file-regular-writable-p file)
    617        (denote-file-has-supported-extension-p file)))
    618 
    619 (define-obsolete-function-alias
    620   'denote--writable-and-supported-p
    621   'denote-file-is-writable-and-supported-p
    622   "1.0.0")
    623 
    624 (defun denote-get-file-name-relative-to-denote-directory (file)
    625   "Return name of FILE relative to the variable `denote-directory'.
    626 FILE must be an absolute path."
    627   (when-let* ((dir (denote-directory))
    628               ((file-name-absolute-p file))
    629               (file-name (expand-file-name file))
    630               ((string-prefix-p dir file-name)))
    631     (substring-no-properties file-name (length dir))))
    632 
    633 (define-obsolete-function-alias
    634   'denote--file-name-relative-to-denote-directory
    635   'denote-get-file-name-relative-to-denote-directory
    636   "1.0.0")
    637 
    638 (defun denote-extract-id-from-string (string)
    639   "Return existing Denote identifier in STRING, else nil."
    640   (when (string-match denote-id-regexp string)
    641     (match-string 0 string)))
    642 
    643 (define-obsolete-function-alias
    644   'denote-link--id-from-string
    645   'denote-extract-id-from-string
    646   "1.0.0")
    647 
    648 ;; TODO 2022-09-26: Maybe we can consolidate this with
    649 ;; `denote--dir-in-denote-directory-p'?  Another check for the
    650 ;; directory prefix is done in `denote-file-is-note-p'.
    651 (defun denote--default-dir-has-denote-prefix ()
    652   "Test `default-directory' for variable `denote-directory' prefix."
    653   (string-prefix-p (denote-directory)
    654                    (expand-file-name default-directory)))
    655 
    656 (defun denote-directory-files ()
    657   "Return list of absolute file paths in variable `denote-directory'.
    658 
    659 Files only need to have an identifier.  The return value may thus
    660 include file types that are not implied by `denote-file-type'.
    661 To limit the return value to text files, use the function
    662 `denote-directory-text-only-files'.
    663 
    664 Remember that the variable `denote-directory' accepts a dir-local
    665 value, as explained in its doc string."
    666   (mapcar
    667    #'expand-file-name
    668    (seq-remove
    669     (lambda (f)
    670       (not (denote-file-has-identifier-p f)))
    671     (directory-files-recursively
    672      (denote-directory)
    673      directory-files-no-dot-files-regexp
    674      :include-directories
    675      (lambda (f)
    676        (cond
    677         ((when-let ((regexp denote-excluded-directories-regexp))
    678            (not (string-match-p regexp f))))
    679         ((file-readable-p f))
    680         (t)))
    681      :follow-symlinks))))
    682 
    683 (defun denote-directory-text-only-files ()
    684   "Return list of text files in variable `denote-directory'.
    685 Filter `denote-directory-files' using `denote-file-is-note-p'."
    686   (seq-filter #'denote-file-is-note-p (denote-directory-files)))
    687 
    688 (define-obsolete-function-alias
    689   'denote--directory-files
    690   'denote-directory-files
    691   "1.0.0")
    692 
    693 (defun denote-directory-subdirectories ()
    694   "Return list of subdirectories in variable `denote-directory'.
    695 Omit dotfiles (such as .git) unconditionally.  Also exclude
    696 whatever matches `denote-excluded-directories-regexp'."
    697   (seq-remove
    698    (lambda (filename)
    699      (let ((rel (denote-get-file-name-relative-to-denote-directory filename)))
    700        (or (not (file-directory-p filename))
    701            (string-match-p "\\`\\." rel)
    702            (string-match-p "/\\." rel)
    703            (when-let ((regexp denote-excluded-directories-regexp))
    704              (string-match-p regexp rel)))))
    705    (directory-files-recursively (denote-directory) ".*" t t)))
    706 
    707 (define-obsolete-function-alias
    708   'denote--subdirs
    709   'denote-directory-subdirectories
    710   "1.0.0")
    711 
    712 (defun denote-get-path-by-id (id)
    713   "Return absolute path of ID string in `denote-directory-files'."
    714   (seq-find
    715    (lambda (f)
    716      (and (string-prefix-p id (file-name-nondirectory f))
    717           ;; The directory can contain exported html and other
    718           ;; derivative files that have the same name sans extetion as
    719           ;; the note.
    720           (denote-file-is-note-p f)))
    721    (denote-directory-files)))
    722 
    723 (define-obsolete-function-alias
    724   'denote--get-note-path-by-id
    725   'denote-get-path-by-id
    726   "1.0.0")
    727 
    728 (defun denote-get-relative-path-by-id (id &optional directory)
    729   "Return relative path of ID string in `denote-directory-files'.
    730 The path is relative to DIRECTORY (default: ‘default-directory’)."
    731   (file-relative-name (denote-get-path-by-id id) directory))
    732 
    733 (defun denote-directory-files-matching-regexp (regexp)
    734   "Return list of files matching REGEXP in `denote-directory-files'."
    735   (seq-filter
    736    (lambda (f)
    737      (string-match-p regexp (denote-get-file-name-relative-to-denote-directory f)))
    738    (denote-directory-files)))
    739 
    740 (define-obsolete-function-alias
    741   'denote--directory-files-matching-regexp
    742   'denote-directory-files-matching-regexp
    743   "1.0.0")
    744 
    745 (defun denote-file-prompt (&optional initial-text)
    746   "Prompt for file with identifier in variable `denote-directory'.
    747 With optional INITIAL-TEXT, use it to prepopulate the minibuffer."
    748   (let* ((project-find-functions #'denote-project-find)
    749          (project (project-current nil (denote-directory)))
    750          (dirs (list (project-root project)))
    751          (all-files (project-files project dirs))
    752          (completion-ignore-case read-file-name-completion-ignore-case))
    753     (funcall project-read-file-name-function
    754              "Select note: " all-files nil 'denote--title-history initial-text)))
    755 
    756 (define-obsolete-function-alias
    757   'denote--retrieve-read-file-prompt
    758   'denote-file-prompt
    759   "1.0.0")
    760 
    761 ;;;; Keywords
    762 
    763 (defun denote-extract-keywords-from-path (path)
    764   "Extract keywords from PATH and return them as a list of strings.
    765 PATH must be a Denote-style file name where keywords are prefixed
    766 with an underscore.
    767 
    768 If PATH has no such keywords, return nil."
    769   (let* ((file-name (file-name-nondirectory path))
    770          (kws (when (string-match denote-keywords-regexp file-name)
    771                 (match-string-no-properties 1 file-name))))
    772     (when kws
    773       (split-string kws "_"))))
    774 
    775 (define-obsolete-function-alias
    776   'denote--extract-keywords-from-path
    777   'denote-extract-keywords-from-path
    778   "1.0.0")
    779 
    780 (defun denote--inferred-keywords ()
    781   "Extract keywords from `denote-directory-files'.
    782 This function returns duplicates.  The `denote-keywords' is the
    783 one that doesn't."
    784   (let ((kw (mapcan #'denote-extract-keywords-from-path (denote-directory-files))))
    785     (if-let ((regexp denote-excluded-keywords-regexp))
    786         (seq-filter (lambda (k) (not (string-match-p regexp k))) kw)
    787       kw)))
    788 
    789 (defun denote-keywords ()
    790   "Return appropriate list of keyword candidates.
    791 If `denote-infer-keywords' is non-nil, infer keywords from
    792 existing notes and combine them into a list with
    793 `denote-known-keywords'.  Else use only the latter.
    794 
    795 Inferred keywords are filtered by the user option
    796 `denote-excluded-keywords-regexp'."
    797   (delete-dups
    798    (if denote-infer-keywords
    799        (append (denote--inferred-keywords) denote-known-keywords)
    800      denote-known-keywords)))
    801 
    802 (defvar denote--keyword-history nil
    803   "Minibuffer history of inputted keywords.")
    804 
    805 (defun denote--keywords-crm (keywords &optional prompt)
    806   "Use `completing-read-multiple' for KEYWORDS.
    807 With optional PROMPT, use it instead of a generic text for file
    808 keywords."
    809   (delete-dups
    810    (completing-read-multiple
    811     (or prompt "File keyword: ") keywords
    812     nil nil nil 'denote--keyword-history)))
    813 
    814 (defun denote-keywords-prompt ()
    815   "Prompt for one or more keywords.
    816 In the case of multiple entries, those are separated by the
    817 `crm-sepator', which typically is a comma.  In such a case, the
    818 output is sorted with `string-lessp'.
    819 
    820 Process the return value with `denote-keywords-sort'."
    821   (denote-keywords-sort (denote--keywords-crm (denote-keywords))))
    822 
    823 (defun denote-keywords-sort (keywords)
    824   "Sort KEYWORDS if `denote-sort-keywords' is non-nil.
    825 KEYWORDS is a list of strings, per `denote-keywords-prompt'."
    826   (if denote-sort-keywords
    827       (sort keywords #'string-lessp)
    828     keywords))
    829 
    830 (define-obsolete-function-alias
    831   'denote--keywords-prompt
    832   'denote-keywords-prompt
    833   "1.0.0")
    834 
    835 (defun denote--keywords-combine (keywords)
    836   "Format KEYWORDS output of `denote-keywords-prompt'."
    837   (mapconcat #'downcase keywords "_"))
    838 
    839 (defun denote--keywords-add-to-history (keywords)
    840   "Append KEYWORDS to `denote--keyword-history'."
    841   (mapc (lambda (kw)
    842           (add-to-history 'denote--keyword-history kw))
    843         (delete-dups keywords)))
    844 
    845 ;;;; File types
    846 
    847 (defvar denote-org-front-matter
    848   "#+title:      %s
    849 #+date:       %s
    850 #+filetags:   %s
    851 #+identifier: %s
    852 \n"
    853   "Org front matter.
    854 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
    855 ID.  Advanced users are advised to consult Info node `(denote)
    856 Change the front matter format'.")
    857 
    858 (defvar denote-yaml-front-matter
    859   "---
    860 title:      %s
    861 date:       %s
    862 tags:       %s
    863 identifier: %S
    864 ---\n\n"
    865   "YAML (Markdown) front matter.
    866 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
    867 ID.  Advanced users are advised to consult Info node `(denote)
    868 Change the front matter format'.")
    869 
    870 (defvar denote-toml-front-matter
    871   "+++
    872 title      = %s
    873 date       = %s
    874 tags       = %s
    875 identifier = %S
    876 +++\n\n"
    877   "TOML (Markdown) front matter.
    878 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
    879 ID.  Advanced users are advised to consult Info node `(denote)
    880 Change the front matter format'.")
    881 
    882 (defvar denote-text-front-matter
    883   "title:      %s
    884 date:       %s
    885 tags:       %s
    886 identifier: %s
    887 ---------------------------\n\n"
    888   "Plain text front matter.
    889 It is passed to `format' with arguments TITLE, DATE, KEYWORDS,
    890 ID.  Advanced users are advised to consult Info node `(denote)
    891 Change the front matter format'.")
    892 
    893 (defun denote-surround-with-quotes (s)
    894   "Surround string S with quotes.
    895 This can be used in `denote-file-types' to format front mattter."
    896   (format "%S" s))
    897 
    898 (defun denote-trim-whitespace (s)
    899   "Trim whitespace around string S.
    900 This can be used in `denote-file-types' to format front mattter."
    901   (if (string-blank-p s)
    902       ""
    903     (let ((trims "[ \t\n\r]+"))
    904       (string-trim s trims trims))))
    905 
    906 (defun denote--trim-quotes (s)
    907   "Trim quotes around string S."
    908   (let ((trims "[\"']+"))
    909     (string-trim s trims trims)))
    910 
    911 (defun denote-trim-whitespace-then-quotes (s)
    912   "Trim whitespace then quotes around string S.
    913 This can be used in `denote-file-types' to format front mattter."
    914   (if (string-blank-p s)
    915       ""
    916     (denote--trim-quotes (denote-trim-whitespace s))))
    917 
    918 (defun denote-format-keywords-for-md-front-matter (keywords)
    919   "Format front matter KEYWORDS for markdown file type.
    920 KEYWORDS is a list of strings.  Consult the `denote-file-types'
    921 for how this is used."
    922   (format "[%s]" (mapconcat (lambda (k) (format "%S" k)) keywords ", ")))
    923 
    924 (defun denote-format-keywords-for-text-front-matter (keywords)
    925   "Format front matter KEYWORDS for text file type.
    926 KEYWORDS is a list of strings.  Consult the `denote-file-types'
    927 for how this is used."
    928   (string-join keywords "  "))
    929 
    930 (defun denote-format-keywords-for-org-front-matter (keywords)
    931   "Format front matter KEYWORDS for org file type.
    932 KEYWORDS is a list of strings.  Consult the `denote-file-types'
    933 for how this is used."
    934   (if keywords
    935       (format ":%s:" (string-join keywords ":"))
    936     ""))
    937 
    938 (defun denote-extract-keywords-from-front-matter (keywords-string)
    939   "Extract keywords list from front matter KEYWORDS-STRING.
    940 Split KEYWORDS-STRING into a list of strings.  If KEYWORDS-STRING
    941 satisfies `string-blank-p', return an empty string.
    942 
    943 Consult the `denote-file-types' for how this is used."
    944   (if (string-blank-p keywords-string)
    945       ""
    946     (split-string keywords-string "[:,\s]+" t "[][ \"']+")))
    947 
    948 (defvar denote-file-types
    949   '((org
    950      :extension ".org"
    951      :date-function denote-date-org-timestamp
    952      :front-matter denote-org-front-matter
    953      :title-key-regexp "^#\\+title\\s-*:"
    954      :title-value-function identity
    955      :title-value-reverse-function denote-trim-whitespace
    956      :keywords-key-regexp "^#\\+filetags\\s-*:"
    957      :keywords-value-function denote-format-keywords-for-org-front-matter
    958      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
    959      :link denote-org-link-format
    960      :link-in-context-regexp denote-org-link-in-context-regexp)
    961     (markdown-yaml
    962      :extension ".md"
    963      :date-function denote-date-rfc3339
    964      :front-matter denote-yaml-front-matter
    965      :title-key-regexp "^title\\s-*:"
    966      :title-value-function denote-surround-with-quotes
    967      :title-value-reverse-function denote-trim-whitespace-then-quotes
    968      :keywords-key-regexp "^tags\\s-*:"
    969      :keywords-value-function denote-format-keywords-for-md-front-matter
    970      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
    971      :link denote-md-link-format
    972      :link-in-context-regexp denote-md-link-in-context-regexp)
    973     (markdown-toml
    974      :extension ".md"
    975      :date-function denote-date-rfc3339
    976      :front-matter denote-toml-front-matter
    977      :title-key-regexp "^title\\s-*="
    978      :title-value-function denote-surround-with-quotes
    979      :title-value-reverse-function denote-trim-whitespace-then-quotes
    980      :keywords-key-regexp "^tags\\s-*="
    981      :keywords-value-function denote-format-keywords-for-md-front-matter
    982      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
    983      :link denote-md-link-format
    984      :link-in-context-regexp denote-md-link-in-context-regexp)
    985     (text
    986      :extension ".txt"
    987      :date-function denote-date-iso-8601
    988      :front-matter denote-text-front-matter
    989      :title-key-regexp "^title\\s-*:"
    990      :title-value-function identity
    991      :title-value-reverse-function denote-trim-whitespace
    992      :keywords-key-regexp "^tags\\s-*:"
    993      :keywords-value-function denote-format-keywords-for-text-front-matter
    994      :keywords-value-reverse-function denote-extract-keywords-from-front-matter
    995      :link denote-org-link-format
    996      :link-in-context-regexp denote-org-link-in-context-regexp))
    997   "Alist of `denote-file-type' and their format properties.
    998 
    999 Each element is of the form (SYMBOL PROPERTY-LIST).  SYMBOL is
   1000 one of those specified in `denote-file-type' or an arbitrary
   1001 symbol that defines a new file type.
   1002 
   1003 PROPERTY-LIST is a plist that consists of 8 elements:
   1004 
   1005 - `:extension' is a string with the file extension including the
   1006   period.
   1007 
   1008 - `:date-function' is a function that can format a date.  See the
   1009   functions `denote-date-iso-8601', `denote-date-rfc3339', and
   1010   `denote-date-org-timestamp'.
   1011 
   1012 - `:front-matter' is either a string passed to `format' or a
   1013   variable holding such a string.  The `format' function accepts
   1014   four arguments, which come from `denote' in this order: TITLE,
   1015   DATE, KEYWORDS, IDENTIFIER.  Read the doc string of `format' on
   1016   how to reorder arguments.
   1017 
   1018 - `:title-key-regexp' is a regular expression that is used to
   1019   retrieve the title line in a file.  The first line matching
   1020   this regexp is considered the title line.
   1021 
   1022 - `:title-value-function' is the function used to format the raw
   1023   title string for inclusion in the front matter (e.g. to
   1024   surround it with quotes).  Use the `identity' function if no
   1025   further processing is required.
   1026 
   1027 - `:title-value-reverse-function' is the function used to
   1028   retrieve the raw title string from the front matter.  It
   1029   performs the reverse of `:title-value-function'.
   1030 
   1031 - `:keywords-key-regexp' is a regular expression used to retrieve
   1032   the keywords' line in the file.  The first line matching this
   1033   regexp is considered the keywords' line.
   1034 
   1035 - `:keywords-value-function' is the function used to format the
   1036   keywords' list of strings as a single string, with appropriate
   1037   delimiters, for inclusion in the front matter.
   1038 
   1039 - `:keywords-value-reverse-function' is the function used to
   1040   retrieve the keywords' value from the front matter.  It
   1041   performs the reverse of the `:keywords-value-function'.
   1042 
   1043 - `:link' is a string, or variable holding a string, that
   1044   specifies the format of a link.  See the variables
   1045   `denote-org-link-format', `denote-md-link-format'.
   1046 
   1047 - `:link-in-context-regexp' is a regular expression that is used
   1048   to match the aforementioned link format.  See the variables
   1049   `denote-org-link-in-context-regexp',`denote-md-link-in-context-regexp'.
   1050 
   1051 If `denote-file-type' is nil, use the first element of this list
   1052 for new note creation.  The default is `org'.")
   1053 
   1054 (defun denote--date-format-function (file-type)
   1055   "Return date format function of FILE-TYPE."
   1056   (plist-get
   1057    (alist-get file-type denote-file-types)
   1058    :date-function))
   1059 
   1060 (defun denote--file-extension (file-type)
   1061   "Return file type extension based on FILE-TYPE."
   1062   (plist-get
   1063    (alist-get file-type denote-file-types)
   1064    :extension))
   1065 
   1066 (defun denote--front-matter (file-type)
   1067   "Return front matter based on FILE-TYPE."
   1068   (let ((prop (plist-get
   1069                (alist-get file-type denote-file-types)
   1070                :front-matter)))
   1071     (if (symbolp prop)
   1072         (symbol-value prop)
   1073       prop)))
   1074 
   1075 (defun denote--title-key-regexp (file-type)
   1076   "Return the title key regexp associated to FILE-TYPE."
   1077   (plist-get
   1078    (alist-get file-type denote-file-types)
   1079    :title-key-regexp))
   1080 
   1081 (defun denote--title-value-function (file-type)
   1082   "Convert title string to a front matter title, per FILE-TYPE."
   1083   (plist-get
   1084    (alist-get file-type denote-file-types)
   1085    :title-value-function))
   1086 
   1087 (defun denote--title-value-reverse-function (file-type)
   1088   "Convert front matter title to the title string, per FILE-TYPE."
   1089   (plist-get
   1090    (alist-get file-type denote-file-types)
   1091    :title-value-reverse-function))
   1092 
   1093 (defun denote--keywords-key-regexp (file-type)
   1094   "Return the keywords key regexp associated to FILE-TYPE."
   1095   (plist-get
   1096    (alist-get file-type denote-file-types)
   1097    :keywords-key-regexp))
   1098 
   1099 (defun denote--keywords-value-function (file-type)
   1100   "Convert keywords' string to front matter keywords, per FILE-TYPE."
   1101   (plist-get
   1102    (alist-get file-type denote-file-types)
   1103    :keywords-value-function))
   1104 
   1105 (defun denote--keywords-value-reverse-function (file-type)
   1106   "Convert front matter keywords to keywords' list, per FILE-TYPE."
   1107   (plist-get
   1108    (alist-get file-type denote-file-types)
   1109    :keywords-value-reverse-function))
   1110 
   1111 (defun denote--link-format (file-type)
   1112   "Return link format extension based on FILE-TYPE."
   1113   (plist-get
   1114    (alist-get file-type denote-file-types)
   1115    :link))
   1116 
   1117 (defun denote--link-in-context-regexp (file-type)
   1118   "Return link regexp in context based on FILE-TYPE."
   1119   (plist-get
   1120    (alist-get file-type denote-file-types)
   1121    :link-in-context-regexp))
   1122 
   1123 (defun denote--extensions ()
   1124   "Return all extensions in `denote-file-types'."
   1125   (delete-dups
   1126    (mapcar (lambda (type)
   1127              (plist-get (cdr type) :extension))
   1128            denote-file-types)))
   1129 
   1130 (defun denote--file-type-keys ()
   1131   "Return all `denote-file-types' keys."
   1132   (delete-dups (mapcar #'car denote-file-types)))
   1133 
   1134 (defun denote--get-title-line-from-front-matter (title file-type)
   1135   "Retrieve title line from front matter based on FILE-TYPE.
   1136 Format TITLE in the title line.  The returned line does not
   1137 contain the newline."
   1138   (let ((front-matter (denote--format-front-matter title "" nil "" file-type))
   1139         (key-regexp (denote--title-key-regexp file-type)))
   1140     (with-temp-buffer
   1141       (insert front-matter)
   1142       (goto-char (point-min))
   1143       (when (re-search-forward key-regexp nil t 1)
   1144         (buffer-substring-no-properties (line-beginning-position) (line-end-position))))))
   1145 
   1146 (defun denote--get-keywords-line-from-front-matter (keywords file-type)
   1147   "Retrieve keywords line from front matter based on FILE-TYPE.
   1148 Format KEYWORDS in the keywords line.  The returned line does not
   1149 contain the newline."
   1150   (let ((front-matter (denote--format-front-matter "" "" keywords "" file-type))
   1151         (key-regexp (denote--keywords-key-regexp file-type)))
   1152     (with-temp-buffer
   1153       (insert front-matter)
   1154       (goto-char (point-min))
   1155       (when (re-search-forward key-regexp nil t 1)
   1156         (buffer-substring-no-properties (line-beginning-position) (line-end-position))))))
   1157 
   1158 ;;;; Front matter or content retrieval functions
   1159 
   1160 (defun denote-retrieve-filename-identifier (file)
   1161   "Extract identifier from FILE name.
   1162 To return an existing identifier or create a new one, refer to
   1163 the function `denote-retrieve-or-create-file-identifier'."
   1164   (if (denote-file-has-identifier-p file)
   1165       (progn
   1166         (string-match denote-id-regexp file)
   1167         (match-string 0 file))
   1168     (error "Cannot find `%s' as a file with a Denote identifier" file)))
   1169 
   1170 (define-obsolete-function-alias
   1171   'denote--retrieve-filename-identifier
   1172   'denote-retrieve-filename-identifier
   1173   "1.0.0")
   1174 
   1175 (defun denote-retrieve-or-create-file-identifier (file &optional date)
   1176   "Return FILE identifier, generating one if appropriate.
   1177 
   1178 The conditions are as follows:
   1179 
   1180 - If FILE has an identifier, return it.
   1181 
   1182 - If FILE does not have an identifier and optional DATE is
   1183   non-nil, invoke `denote-prompt-for-date-return-id'.
   1184 
   1185 - If FILE does not have an identifier and DATE is nil, use the
   1186   file attributes to determine the last modified date and format
   1187   it as an identifier.
   1188 
   1189 - As a fallback, derive an identifier from the current time.
   1190 
   1191 To only return an existing identifier, refer to the function
   1192 `denote-retrieve-filename-identifier'."
   1193   (cond
   1194    ((string-match denote-id-regexp file)
   1195     (substring file (match-beginning 0) (match-end 0)))
   1196    (date (denote-prompt-for-date-return-id))
   1197    ((denote--file-attributes-time file))
   1198    (t (format-time-string denote-id-format))))
   1199 
   1200 (define-obsolete-function-alias
   1201   'denote--file-name-id
   1202   'denote-retrieve-or-create-file-identifier
   1203   "1.0.0")
   1204 
   1205 (defun denote-retrieve-filename-title (file)
   1206   "Extract title from FILE name, else return `file-name-base'.
   1207 Run `denote-desluggify' on title if the extraction is sucessful."
   1208   (if-let* (((file-exists-p file))
   1209             ((denote-file-has-identifier-p file))
   1210             ((string-match denote-title-regexp file))
   1211             (title (match-string 1 file)))
   1212       (denote-desluggify title)
   1213     (file-name-base file)))
   1214 
   1215 (define-obsolete-function-alias
   1216   'denote--retrieve-filename-title
   1217   'denote-retrieve-filename-title
   1218   "1.0.0")
   1219 
   1220 (defun denote-retrieve-title-value (file file-type)
   1221   "Return title value from FILE front matter per FILE-TYPE."
   1222   (with-temp-buffer
   1223     (insert-file-contents file)
   1224     (goto-char (point-min))
   1225     (when (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   1226       (funcall (denote--title-value-reverse-function file-type)
   1227                (buffer-substring-no-properties (point) (line-end-position))))))
   1228 
   1229 (define-obsolete-function-alias
   1230   'denote--retrieve-title-value
   1231   'denote-retrieve-title-value
   1232   "1.0.0")
   1233 
   1234 (defun denote-retrieve-title-line (file file-type)
   1235   "Return title line from FILE front matter per FILE-TYPE."
   1236   (with-temp-buffer
   1237     (insert-file-contents file)
   1238     (goto-char (point-min))
   1239     (when (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   1240       (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
   1241 
   1242 (define-obsolete-function-alias
   1243   'denote--retrieve-title-line
   1244   'denote-retrieve-title-line
   1245   "1.0.0")
   1246 
   1247 (defun denote-retrieve-keywords-value (file file-type)
   1248   "Return keywords value from FILE front matter per FILE-TYPE."
   1249   (with-temp-buffer
   1250     (insert-file-contents file)
   1251     (goto-char (point-min))
   1252     (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1253       (funcall (denote--keywords-value-reverse-function file-type)
   1254                (buffer-substring-no-properties (point) (line-end-position))))))
   1255 
   1256 (define-obsolete-function-alias
   1257   'denote--retrieve-keywords-value
   1258   'denote-retrieve-keywords-value
   1259   "1.0.0")
   1260 
   1261 (defun denote-retrieve-keywords-line (file file-type)
   1262   "Return keywords line from FILE front matter per FILE-TYPE."
   1263   (with-temp-buffer
   1264     (insert-file-contents file)
   1265     (goto-char (point-min))
   1266     (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1267       (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
   1268 
   1269 (define-obsolete-function-alias
   1270   'denote--retrieve-keywords-line
   1271   'denote-retrieve-keywords-line
   1272   "1.0.0")
   1273 
   1274 (defun denote--retrieve-title-or-filename (file type)
   1275   "Return appropriate title for FILE given its TYPE."
   1276   (if-let (((denote-file-is-note-p file))
   1277            (title (denote-retrieve-title-value file type))
   1278            ((not (string-blank-p title))))
   1279       title
   1280     (denote-retrieve-filename-title file)))
   1281 
   1282 (defun denote--retrieve-files-in-xrefs (identifier)
   1283   "Return sorted, deduplicated file names from IDENTIFIER."
   1284   (sort
   1285    (delete-dups
   1286     (mapcar #'xref-location-group
   1287             (mapcar #'xref-match-item-location
   1288                     (xref-matches-in-files identifier
   1289                                            (denote-directory-text-only-files)))))
   1290    #'string-lessp))
   1291 
   1292 ;;;; New note
   1293 
   1294 ;;;;; Common helpers for new notes
   1295 
   1296 (defun denote-format-file-name (path id keywords title-slug extension)
   1297   "Format file name.
   1298 PATH, ID, KEYWORDS, TITLE-SLUG are expected to be supplied by
   1299 `denote' or equivalent: they will all be converted into a single
   1300 string.  EXTENSION is the file type extension, as a string."
   1301   (let ((kws (denote--keywords-combine keywords))
   1302         (file-name (concat path id)))
   1303     (when (and title-slug (not (string-empty-p title-slug)))
   1304       (setq file-name (concat file-name "--" title-slug)))
   1305     (when (and keywords (not (string-blank-p kws)))
   1306       (setq file-name (concat file-name "__" kws)))
   1307     (concat file-name extension)))
   1308 
   1309 (define-obsolete-function-alias
   1310   'denote--format-file
   1311   'denote-format-file-name
   1312   "1.0.0")
   1313 
   1314 (defun denote--format-front-matter-title (title file-type)
   1315   "Format TITLE according to FILE-TYPE for the file's front matter."
   1316   (funcall (denote--title-value-function file-type) title))
   1317 
   1318 (defun denote--format-front-matter-keywords (keywords file-type)
   1319   "Format KEYWORDS according to FILE-TYPE for the file's front matter.
   1320 Apply `downcase' to KEYWORDS."
   1321   (let ((kw (mapcar #'downcase (denote-sluggify-keywords keywords))))
   1322     (funcall (denote--keywords-value-function file-type) kw)))
   1323 
   1324 (make-obsolete-variable 'denote-text-front-matter-delimiter nil "0.6.0")
   1325 
   1326 (defun denote--format-front-matter (title date keywords id filetype)
   1327   "Front matter for new notes.
   1328 
   1329 TITLE, DATE, KEYWORDS, FILENAME, ID are all strings which are
   1330 provided by `denote'.  FILETYPE is one of the values of
   1331 `denote-file-type'."
   1332   (let* ((fm (denote--front-matter filetype))
   1333          (title (denote--format-front-matter-title title filetype))
   1334          (kws (denote--format-front-matter-keywords keywords filetype)))
   1335     (if fm (format fm title date kws id) "")))
   1336 
   1337 (defun denote--path (title keywords dir id file-type)
   1338   "Return path to new file with ID, TITLE, KEYWORDS and FILE-TYPE in DIR."
   1339   (denote-format-file-name
   1340    dir id
   1341    (denote-sluggify-keywords keywords)
   1342    (denote-sluggify title)
   1343    (denote--file-extension file-type)))
   1344 
   1345 ;; Adapted from `org-hugo--org-date-time-to-rfc3339' in the `ox-hugo'
   1346 ;; package: <https://github.com/kaushalmodi/ox-hugo>.
   1347 (defun denote-date-rfc3339 (date)
   1348   "Format DATE using the RFC3339 specification."
   1349   (replace-regexp-in-string
   1350    "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2"
   1351    (format-time-string "%FT%T%z" date)))
   1352 
   1353 (define-obsolete-function-alias
   1354   'denote--date-rfc3339
   1355   'denote-date-rfc3339
   1356   "1.2.0")
   1357 
   1358 (defun denote-date-org-timestamp (date)
   1359   "Format DATE using the Org inactive timestamp notation."
   1360   (format-time-string "[%F %a %R]" date))
   1361 
   1362 (define-obsolete-function-alias
   1363   'denote--date-org-timestamp
   1364   'denote-date-org-timestamp
   1365   "1.2.0")
   1366 
   1367 (defun denote-date-iso-8601 (date)
   1368   "Format DATE according to ISO 8601 standard."
   1369   (format-time-string "%F" date))
   1370 
   1371 (define-obsolete-function-alias
   1372   'denote--date-iso-8601
   1373   'denote-date-iso-8601
   1374   "1.2.0")
   1375 
   1376 (defun denote--date (date file-type)
   1377   "Expand DATE in an appropriate format for FILE-TYPE."
   1378   (let ((format denote-date-format))
   1379     (cond
   1380      ((stringp format)
   1381       (format-time-string format date))
   1382      ((when-let ((fn (denote--date-format-function file-type)))
   1383         (funcall fn date)))
   1384      (t
   1385       (denote-date-org-timestamp date)))))
   1386 
   1387 (defun denote--prepare-note (title keywords date id directory file-type template)
   1388   "Prepare a new note file.
   1389 
   1390 Arguments TITLE, KEYWORDS, DATE, ID, DIRECTORY, FILE-TYPE,
   1391 and TEMPLATE should be valid for note creation."
   1392   (let* ((path (denote--path title keywords directory id file-type))
   1393          (buffer (find-file path))
   1394          (header (denote--format-front-matter
   1395                   title (denote--date date file-type) keywords
   1396                   (format-time-string denote-id-format date)
   1397                   file-type)))
   1398     (with-current-buffer buffer
   1399       (insert header)
   1400       (insert template))))
   1401 
   1402 (defun denote--dir-in-denote-directory-p (directory)
   1403   "Return DIRECTORY if in variable `denote-directory', else nil."
   1404   (when (and directory
   1405              (string-prefix-p (denote-directory)
   1406                               (expand-file-name directory)))
   1407     directory))
   1408 
   1409 (defun denote--valid-file-type (filetype)
   1410   "Return a valid filetype given the argument FILETYPE.
   1411 If none is found, the first element of `denote-file-types' is
   1412 returned."
   1413   (unless (or (symbolp filetype) (stringp filetype))
   1414     (user-error "`%s' is not a symbol or string" filetype))
   1415   (when (stringp filetype)
   1416     (setq filetype (intern filetype)))
   1417   (if (memq filetype (mapcar 'car denote-file-types))
   1418       filetype
   1419     (caar denote-file-types)))
   1420 
   1421 (defun denote--date-add-current-time (date)
   1422   "Add current time to DATE, if necessary.
   1423 The idea is to turn 2020-01-15 into 2020-01-15 16:19 so that the
   1424 hour and minute component is not left to 00:00.
   1425 
   1426 This reduces the burden on the user who would otherwise need to
   1427 input that value in order to avoid the error of duplicate
   1428 identifiers.
   1429 
   1430 It also addresses a difference between Emacs 28 and Emacs 29
   1431 where the former does not read dates without a time component."
   1432   (if (<= (length date) 10)
   1433       (format "%s %s" date (format-time-string "%H:%M:%S" (current-time)))
   1434     date))
   1435 
   1436 (defun denote--valid-date (date)
   1437   "Return DATE if parsed by `date-to-time', else signal error."
   1438   (let ((datetime (denote--date-add-current-time date)))
   1439     (date-to-time datetime)))
   1440 
   1441 (defun denote--buffer-file-names ()
   1442   "Return file names of active buffers."
   1443   (seq-filter
   1444    #'denote-file-is-note-p
   1445    (delq nil
   1446          (mapcar
   1447           #'buffer-file-name
   1448           (buffer-list)))))
   1449 
   1450 ;; In normal usage, this should only be relevant for `denote-date',
   1451 ;; otherwise the identifier is always unique (we trust that no-one
   1452 ;; writes multiple notes within fractions of a second).  Though the
   1453 ;; `denote' command does call `denote-barf-duplicate-id'.
   1454 (defun denote--id-exists-p (identifier)
   1455   "Return non-nil if IDENTIFIER already exists."
   1456   (seq-some (lambda (file)
   1457               (string-prefix-p identifier (file-name-nondirectory file)))
   1458             (append (denote-directory-files)
   1459                     (denote--buffer-file-names))))
   1460 
   1461 (defun denote-barf-duplicate-id (identifier)
   1462   "Throw a `user-error' if IDENTIFIER already exists."
   1463   (when (denote--id-exists-p identifier)
   1464     (user-error "`%s' already exists; aborting new note creation" identifier)))
   1465 
   1466 (define-obsolete-function-alias
   1467   'denote--barf-duplicate-id
   1468   'denote-barf-duplicate-id
   1469   "1.0.0")
   1470 
   1471 ;;;;; The `denote' command and its prompts
   1472 
   1473 ;;;###autoload
   1474 (defun denote (&optional title keywords file-type subdirectory date template)
   1475   "Create a new note with the appropriate metadata and file name.
   1476 
   1477 When called interactively, the metadata and file name are prompted
   1478 according to the value of `denote-prompts'.
   1479 
   1480 When called from Lisp, all arguments are optional.
   1481 
   1482 - TITLE is a string or a function returning a string.
   1483 
   1484 - KEYWORDS is a list of strings.  The list can be empty or the
   1485   value can be set to nil.
   1486 
   1487 - FILE-TYPE is a symbol among those described in `denote-file-type'.
   1488 
   1489 - SUBDIRECTORY is a string representing the path to either the
   1490   value of the variable `denote-directory' or a subdirectory
   1491   thereof.  The subdirectory must exist: Denote will not create
   1492   it.  If SUBDIRECTORY does not resolve to a valid path, the
   1493   variable `denote-directory' is used instead.
   1494 
   1495 - DATE is a string representing a date like 2022-06-30 or a date
   1496   and time like 2022-06-16 14:30.  A nil value or an empty string
   1497   is interpreted as the `current-time'.
   1498 
   1499 - TEMPLATE is a symbol which represents the key of a cons cell in
   1500   the user option `denote-templates'.  The value of that key is
   1501   inserted to the newly created buffer after the front matter."
   1502   (interactive
   1503    (let ((args (make-vector 6 nil)))
   1504      (dolist (prompt denote-prompts)
   1505        (pcase prompt
   1506          ('title (aset args 0 (denote-title-prompt
   1507                                (when (use-region-p)
   1508                                  (buffer-substring-no-properties
   1509                                   (region-beginning)
   1510                                   (region-end))))))
   1511          ('keywords (aset args 1 (denote-keywords-prompt)))
   1512          ('file-type (aset args 2 (denote-file-type-prompt)))
   1513          ('subdirectory (aset args 3 (denote-subdirectory-prompt)))
   1514          ('date (aset args 4 (denote-date-prompt)))
   1515          ('template (aset args 5 (denote-template-prompt)))))
   1516      (append args nil)))
   1517   (let* ((title (or title ""))
   1518          (file-type (denote--valid-file-type (or file-type denote-file-type)))
   1519          (kws (if (called-interactively-p 'interactive)
   1520                   keywords
   1521                 (denote-keywords-sort keywords)))
   1522          (date (if (or (null date) (string-empty-p date))
   1523                    (current-time)
   1524                  (denote--valid-date date)))
   1525          (id (format-time-string denote-id-format date))
   1526          (directory (if (denote--dir-in-denote-directory-p subdirectory)
   1527                         (file-name-as-directory subdirectory)
   1528                       (denote-directory)))
   1529          (template (if (stringp template)
   1530                        template
   1531                      (or (alist-get template denote-templates) ""))))
   1532     (denote-barf-duplicate-id id)
   1533     (denote--prepare-note title kws date id directory file-type template)
   1534     (denote--keywords-add-to-history keywords)))
   1535 
   1536 (defvar denote--title-history nil
   1537   "Minibuffer history of `denote-title-prompt'.")
   1538 
   1539 (defun denote-title-prompt (&optional default-title)
   1540   "Read file title for `denote'.
   1541 With optional DEFAULT-TITLE use it as the default value."
   1542   (let* ((def default-title)
   1543          (format (if (and def (not (string-empty-p def)))
   1544                      (format "File title [%s]: " def)
   1545                    "File title: ")))
   1546     (read-string format nil 'denote--title-history def)))
   1547 
   1548 (define-obsolete-function-alias
   1549   'denote--title-prompt
   1550   'denote-title-prompt
   1551   "1.0.0")
   1552 
   1553 (defvar denote--file-type-history nil
   1554   "Minibuffer history of `denote-file-type-prompt'.")
   1555 
   1556 (defun denote-file-type-prompt ()
   1557   "Prompt for `denote-file-type'.
   1558 Note that a non-nil value other than `text', `markdown-yaml', and
   1559 `markdown-toml' falls back to an Org file type.  We use `org'
   1560 here for clarity."
   1561   (completing-read
   1562    "Select file type: " (denote--file-type-keys) nil t
   1563    nil 'denote--file-type-history))
   1564 
   1565 (define-obsolete-function-alias
   1566   'denote--file-type-prompt
   1567   'denote-file-type-prompt
   1568   "1.0.0")
   1569 
   1570 (defvar denote--date-history nil
   1571   "Minibuffer history of `denote-date-prompt'.")
   1572 
   1573 (declare-function org-read-date "org" (&optional with-time to-time from-string prompt default-time default-input inactive))
   1574 
   1575 (defun denote-date-prompt ()
   1576   "Prompt for date, expecting YYYY-MM-DD or that plus HH:MM.
   1577 Use Org's more advanced date selection utility if the user option
   1578 `denote-date-prompt-use-org-read-date' is non-nil."
   1579   (if (and denote-date-prompt-use-org-read-date
   1580            (require 'org nil :no-error))
   1581       (let* ((time (org-read-date nil t))
   1582              (org-time-seconds (format-time-string "%S" time))
   1583              (cur-time-seconds (format-time-string "%S" (current-time))))
   1584         ;; When the user does not input a time, org-read-date defaults to 00 for seconds.
   1585         ;; When the seconds are 00, we add the current seconds to avoid identifier collisions.
   1586         (when (string-equal "00" org-time-seconds)
   1587           (setq time (time-add time (string-to-number cur-time-seconds))))
   1588         (format-time-string "%Y-%m-%d %H:%M:%S" time))
   1589     (read-string
   1590      "DATE and TIME for note (e.g. 2022-06-16 14:30): "
   1591      nil 'denote--date-history)))
   1592 
   1593 (define-obsolete-function-alias
   1594   'denote--date-prompt
   1595   'denote-date-prompt
   1596   "1.0.0")
   1597 
   1598 (defun denote-prompt-for-date-return-id ()
   1599   "Use `denote-date-prompt' and return it as `denote-id-format'."
   1600   (format-time-string
   1601    denote-id-format
   1602    (denote--valid-date (denote-date-prompt))))
   1603 
   1604 (defvar denote--subdir-history nil
   1605   "Minibuffer history of `denote-subdirectory-prompt'.")
   1606 
   1607 ;; Making it a completion table is useful for packages that read the
   1608 ;; metadata, such as `marginalia' and `embark'.
   1609 (defun denote--subdirs-completion-table (dirs)
   1610   "Match DIRS as a completion table."
   1611   (let* ((def (car denote--subdir-history))
   1612          (table (denote--completion-table 'file dirs))
   1613          (prompt (if def
   1614                      (format "Select subdirectory [%s]: " def)
   1615                    "Select subdirectory: ")))
   1616     (completing-read prompt table nil t nil 'denote--subdir-history def)))
   1617 
   1618 (defun denote-subdirectory-prompt ()
   1619   "Prompt for subdirectory of the variable `denote-directory'.
   1620 The table uses the `file' completion category (so it works with
   1621 packages such as `marginalia' and `embark')."
   1622   (let* ((root (directory-file-name (denote-directory)))
   1623          (subdirs (denote-directory-subdirectories))
   1624          (dirs (push root subdirs)))
   1625     (denote--subdirs-completion-table dirs)))
   1626 
   1627 (define-obsolete-function-alias
   1628   'denote--subdirs-prompt
   1629   'denote-subdirectory-prompt
   1630   "1.0.0")
   1631 
   1632 (defvar denote--template-history nil
   1633   "Minibuffer history of `denote-template-prompt'.")
   1634 
   1635 (defun denote-template-prompt ()
   1636   "Prompt for template key in `denote-templates' and return its value."
   1637   (let ((templates denote-templates))
   1638     (alist-get
   1639      (intern
   1640       (completing-read
   1641        "Select template KEY: " (mapcar #'car templates)
   1642        nil t nil 'denote--template-history))
   1643      templates)))
   1644 
   1645 (define-obsolete-function-alias
   1646   'denote--template-prompt
   1647   'denote-template-prompt
   1648   "1.0.0")
   1649 
   1650 ;;;;; Convenience commands as `denote' variants
   1651 
   1652 (defalias 'denote-create-note (symbol-function 'denote))
   1653 
   1654 ;;;###autoload
   1655 (defun denote-type ()
   1656   "Create note while prompting for a file type.
   1657 
   1658 This is the equivalent to calling `denote' when `denote-prompts'
   1659 is set to \\='(file-type title keywords)."
   1660   (declare (interactive-only t))
   1661   (interactive)
   1662   (let ((denote-prompts '(file-type title keywords)))
   1663     (call-interactively #'denote)))
   1664 
   1665 (defalias 'denote-create-note-using-type (symbol-function 'denote-type))
   1666 
   1667 ;;;###autoload
   1668 (defun denote-date ()
   1669   "Create note while prompting for a date.
   1670 
   1671 The date can be in YEAR-MONTH-DAY notation like 2022-06-30 or
   1672 that plus the time: 2022-06-16 14:30.  When the user option
   1673 `denote-date-prompt-use-org-read-date' is non-nil, the date
   1674 prompt uses the more powerful Org+calendar system.
   1675 
   1676 This is the equivalent to calling `denote' when `denote-prompts'
   1677 is set to \\='(date title keywords)."
   1678   (declare (interactive-only t))
   1679   (interactive)
   1680   (let ((denote-prompts '(date title keywords)))
   1681     (call-interactively #'denote)))
   1682 
   1683 (defalias 'denote-create-note-using-date (symbol-function 'denote-date))
   1684 
   1685 ;;;###autoload
   1686 (defun denote-subdirectory ()
   1687   "Create note while prompting for a subdirectory.
   1688 
   1689 Available candidates include the value of the variable
   1690 `denote-directory' and any subdirectory thereof.
   1691 
   1692 This is equivalent to calling `denote' when `denote-prompts' is
   1693 set to \\='(subdirectory title keywords)."
   1694   (declare (interactive-only t))
   1695   (interactive)
   1696   (let ((denote-prompts '(subdirectory title keywords)))
   1697     (call-interactively #'denote)))
   1698 
   1699 (defalias 'denote-create-note-in-subdirectory (symbol-function 'denote-subdirectory))
   1700 
   1701 ;;;###autoload
   1702 (defun denote-template ()
   1703   "Create note while prompting for a template.
   1704 
   1705 Available candidates include the keys in the `denote-templates'
   1706 alist.  The value of the selected key is inserted in the newly
   1707 created note after the front matter.
   1708 
   1709 This is equivalent to calling `denote' when `denote-prompts' is
   1710 set to \\='(template title keywords)."
   1711   (declare (interactive-only t))
   1712   (interactive)
   1713   (let ((denote-prompts '(template title keywords)))
   1714     (call-interactively #'denote)))
   1715 
   1716 (defalias 'denote-create-note-with-template (symbol-function 'denote-template))
   1717 
   1718 ;;;;; Other convenience commands
   1719 
   1720 (defun denote--extract-title-from-file-history ()
   1721   "Extract last file title input from `file-name-history'."
   1722   ;; We do not need to check if `file-name-history' is initialised
   1723   ;; because it is defined in files.el.  My understanding is that it
   1724   ;; is always loaded.
   1725   (when-let ((title (expand-file-name (car file-name-history))))
   1726     (string-match (denote-directory) title)
   1727     (substring title (match-end 0))))
   1728 
   1729 ;;;###autoload
   1730 (defun denote-open-or-create (target)
   1731   "Visit TARGET file in variable `denote-directory'.
   1732 If file does not exist, invoke `denote' to create a file.
   1733 
   1734 If TARGET file does not exist, add the user input that was used
   1735 to search for it to the minibuffer history of the
   1736 `denote-title-prompt'.  The user can then retrieve and possibly
   1737 further edit their last input, using it as the newly created
   1738 note's actual title.  At the `denote-title-prompt' type
   1739 \\<minibuffer-local-map>\\[previous-history-element]."
   1740   (interactive (list (denote-file-prompt)))
   1741   (if (file-exists-p target)
   1742       (find-file target)
   1743     (call-interactively #'denote)))
   1744 
   1745 ;;;###autoload
   1746 (defun denote-keywords-add (keywords)
   1747   "Prompt for KEYWORDS to add to the current note's front matter.
   1748 When called from Lisp, KEYWORDS is a list of strings.
   1749 
   1750 Rename the file without further prompt so that its name reflects
   1751 the new front matter, per `denote-rename-file-using-front-matter'."
   1752   (interactive (list (denote-keywords-prompt)))
   1753   ;; A combination of if-let and let, as we need to take into account
   1754   ;; the scenario in which there are no keywords yet.
   1755   (if-let* ((file (buffer-file-name))
   1756             ((denote-file-is-note-p file))
   1757             (file-type (denote-filetype-heuristics file)))
   1758       (let* ((cur-keywords (denote-retrieve-keywords-value file file-type))
   1759              (new-keywords (if (and (stringp cur-keywords)
   1760                                     (string-blank-p cur-keywords))
   1761                                keywords
   1762                              (denote-keywords-sort
   1763                               (seq-uniq (append keywords cur-keywords))))))
   1764         (denote--rewrite-keywords file new-keywords file-type)
   1765         (denote-rename-file-using-front-matter file t))
   1766     (user-error "Buffer not visiting a Denote file")))
   1767 
   1768 (defun denote--keywords-delete-prompt (keywords)
   1769   "Prompt for one or more KEYWORDS.
   1770 In the case of multiple entries, those are separated by the
   1771 `crm-sepator', which typically is a comma.  In such a case, the
   1772 output is sorted with `string-lessp'."
   1773   (let ((choice (denote--keywords-crm keywords "Keyword to remove: ")))
   1774     (if denote-sort-keywords
   1775         (sort choice #'string-lessp)
   1776       choice)))
   1777 
   1778 ;;;###autoload
   1779 (defun denote-keywords-remove ()
   1780   "Prompt for keywords in current note and remove them.
   1781 Keywords are retrieved from the file's front matter.
   1782 
   1783 Rename the file without further prompt so that its name reflects
   1784 the new front matter, per `denote-rename-file-using-front-matter'."
   1785   (declare (interactive-only t))
   1786   (interactive)
   1787   (if-let* ((file (buffer-file-name))
   1788             ((denote-file-is-note-p file))
   1789             (file-type (denote-filetype-heuristics file)))
   1790       (when-let* ((cur-keywords (denote-retrieve-keywords-value file file-type))
   1791                   ((or (listp cur-keywords) (not (string-blank-p cur-keywords))))
   1792                   (del-keyword (denote--keywords-delete-prompt cur-keywords)))
   1793         (denote--rewrite-keywords
   1794          file
   1795          (seq-difference cur-keywords del-keyword)
   1796          file-type)
   1797         (denote-rename-file-using-front-matter file t))
   1798     (user-error "Buffer not visiting a Denote file")))
   1799 
   1800 ;;;; Note modification
   1801 
   1802 ;;;;; Common helpers for note modifications
   1803 
   1804 (defun denote--file-types-with-extension (extension)
   1805   "Return only the entries of `denote-file-types' with EXTENSION.
   1806 See the format of `denote-file-types'."
   1807   (seq-filter (lambda (type)
   1808                 (string-equal (plist-get (cdr type) :extension) extension))
   1809               denote-file-types))
   1810 
   1811 (defun denote-filetype-heuristics (file)
   1812   "Return likely file type of FILE.
   1813 Use the file extension to detect the file type of the file.
   1814 
   1815 If more than one file type correspond to this file extension, use
   1816 the first file type for which the key-title-kegexp matches in the
   1817 file or, if none matches, use the first type with this file
   1818 extension in `denote-file-type'.
   1819 
   1820 If no file types in `denote-file-types' has the file extension,
   1821 the file type is assumed to be the first of `denote-file-types'."
   1822   (let* ((file-type)
   1823          (extension (file-name-extension file t))
   1824          (types (denote--file-types-with-extension extension)))
   1825     (cond ((not types)
   1826            (setq file-type (caar denote-file-types)))
   1827           ((= (length types) 1)
   1828            (setq file-type (caar types)))
   1829           (t
   1830            (if-let ((found-type
   1831                      (seq-find
   1832                       (lambda (type)
   1833                         (denote--regexp-in-file-p (plist-get (cdr type) :title-key-regexp) file))
   1834                       types)))
   1835                (setq file-type (car found-type))
   1836              (setq file-type (caar types)))))
   1837     file-type))
   1838 
   1839 (define-obsolete-function-alias
   1840   'denote--filetype-heuristics
   1841   'denote-filetype-heuristics
   1842   "1.0.0")
   1843 
   1844 (defun denote--file-attributes-time (file)
   1845   "Return `file-attribute-modification-time' of FILE as identifier."
   1846   (format-time-string
   1847    denote-id-format
   1848    (file-attribute-modification-time (file-attributes file))))
   1849 
   1850 (defun denote-update-dired-buffers ()
   1851   "Update Dired buffers of variable `denote-directory'."
   1852   (mapc
   1853    (lambda (buf)
   1854      (with-current-buffer buf
   1855        (when (and (eq major-mode 'dired-mode)
   1856                   (denote--default-dir-has-denote-prefix))
   1857          (revert-buffer))))
   1858    (buffer-list)))
   1859 
   1860 (defun denote--rename-buffer (old-name new-name)
   1861   "Rename OLD-NAME buffer to NEW-NAME, when appropriate."
   1862   (when-let ((buffer (find-buffer-visiting old-name)))
   1863     (with-current-buffer buffer
   1864       (set-visited-file-name new-name nil t))))
   1865 
   1866 (defun denote-rename-file-and-buffer (old-name new-name)
   1867   "Rename file named OLD-NAME to NEW-NAME, updating buffer name."
   1868   (unless (string= (expand-file-name old-name) (expand-file-name new-name))
   1869     (cond
   1870      ((derived-mode-p 'dired-mode)
   1871       (dired-rename-file old-name new-name nil))
   1872      ((vc-backend old-name)
   1873       (vc-rename-file old-name new-name))
   1874      (t
   1875       (rename-file old-name new-name nil)))
   1876     (denote--rename-buffer old-name new-name)))
   1877 
   1878 (define-obsolete-function-alias
   1879   'denote--rename-file
   1880   'denote-rename-file-and-buffer
   1881   "1.0.0")
   1882 
   1883 (defun denote--add-front-matter (file title keywords id file-type)
   1884   "Prepend front matter to FILE if `denote-file-is-note-p'.
   1885 The TITLE, KEYWORDS ID, and FILE-TYPE are passed from the
   1886 renaming command and are used to construct a new front matter
   1887 block if appropriate."
   1888   (when-let* ((date (denote--date (date-to-time id) file-type))
   1889               (new-front-matter (denote--format-front-matter title date keywords id file-type)))
   1890     (with-current-buffer (find-file-noselect file)
   1891       (goto-char (point-min))
   1892       (insert new-front-matter))))
   1893 
   1894 (defun denote--regexp-in-file-p (regexp file)
   1895   "Return t if REGEXP matches in the FILE."
   1896   (with-temp-buffer
   1897     (insert-file-contents file)
   1898     (goto-char (point-min))
   1899     (re-search-forward regexp nil t 1)))
   1900 
   1901 (defun denote--edit-front-matter-p (file file-type)
   1902   "Test if FILE should be subject to front matter rewrite.
   1903 Use FILE-TYPE to look for the front matter lines.  This is
   1904 relevant for operations that insert or rewrite the front matter
   1905 in a Denote note.
   1906 
   1907 For the purposes of this test, FILE is a Denote note when it
   1908 contains a title line, a keywords line or both."
   1909   (and (denote--regexp-in-file-p (denote--title-key-regexp file-type) file)
   1910        (denote--regexp-in-file-p (denote--keywords-key-regexp file-type) file)))
   1911 
   1912 (defun denote--rewrite-keywords (file keywords file-type)
   1913   "Rewrite KEYWORDS in FILE outright according to FILE-TYPE.
   1914 
   1915 Do the same as `denote--rewrite-front-matter' for keywords,
   1916 but do not ask for confirmation.
   1917 
   1918 This is for use in `denote-dired-rename-marked-files' or related.
   1919 Those commands ask for confirmation once before performing an
   1920 operation on multiple files."
   1921   (with-current-buffer (find-file-noselect file)
   1922     (save-excursion
   1923       (save-restriction
   1924         (widen)
   1925         (goto-char (point-min))
   1926         (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1927           (goto-char (line-beginning-position))
   1928           (insert (denote--get-keywords-line-from-front-matter keywords file-type))
   1929           (delete-region (point) (line-end-position)))))))
   1930 
   1931 (defun denote--rewrite-front-matter (file title keywords file-type)
   1932   "Rewrite front matter of note after `denote-dired-rename-file'.
   1933 The FILE, TITLE, KEYWORDS, and FILE-TYPE are passed from the
   1934 renaming command and are used to construct new front matter
   1935 values if appropriate."
   1936   (when-let* ((old-title-line (denote-retrieve-title-line file file-type))
   1937               (old-keywords-line (denote-retrieve-keywords-line file file-type))
   1938               (new-title-line (denote--get-title-line-from-front-matter title file-type))
   1939               (new-keywords-line (denote--get-keywords-line-from-front-matter keywords file-type)))
   1940     (with-current-buffer (find-file-noselect file)
   1941       (when (y-or-n-p (format
   1942                        "Replace front matter?\n-%s\n+%s\n\n-%s\n+%s?"
   1943                        (propertize old-title-line 'face 'error)
   1944                        (propertize new-title-line 'face 'success)
   1945                        (propertize old-keywords-line 'face 'error)
   1946                        (propertize new-keywords-line 'face 'success)))
   1947         (save-excursion
   1948           (save-restriction
   1949             (widen)
   1950             (goto-char (point-min))
   1951             (re-search-forward (denote--title-key-regexp file-type) nil t 1)
   1952             (goto-char (line-beginning-position))
   1953             (insert new-title-line)
   1954             (delete-region (point) (line-end-position))
   1955             (goto-char (point-min))
   1956             (re-search-forward (denote--keywords-key-regexp file-type) nil t 1)
   1957             (goto-char (line-beginning-position))
   1958             (insert new-keywords-line)
   1959             (delete-region (point) (line-end-position))))))))
   1960 
   1961 ;;;;; The renaming commands and their prompts
   1962 
   1963 (defun denote--rename-dired-file-or-prompt ()
   1964   "Return Dired file at point, else prompt for one.
   1965 Throw error is FILE is not regular, else return FILE."
   1966   (or (dired-get-filename nil t)
   1967       (let* ((file (buffer-file-name))
   1968              (format (if file
   1969                          (format "Rename file Denote-style [%s]: " file)
   1970                        "Rename file Denote-style: "))
   1971              (selected-file (read-file-name format nil file t nil)))
   1972         (if (or (file-directory-p selected-file)
   1973                 (not (file-regular-p selected-file)))
   1974             (user-error "Only rename regular files")
   1975           selected-file))))
   1976 
   1977 (defun denote-rename-file-prompt (old-name new-name)
   1978   "Prompt to rename file named OLD-NAME to NEW-NAME."
   1979   (unless (string= (expand-file-name old-name) (expand-file-name new-name))
   1980     (y-or-n-p
   1981      (format "Rename %s to %s?"
   1982              (propertize (file-name-nondirectory old-name) 'face 'error)
   1983              (propertize (file-name-nondirectory new-name) 'face 'success)))))
   1984 
   1985 (define-obsolete-function-alias
   1986   'denote--rename-file-prompt
   1987   'denote-rename-file-prompt
   1988   "1.0.0")
   1989 
   1990 ;;;###autoload
   1991 (defun denote-rename-file (file title keywords &optional date)
   1992   "Rename file and update existing front matter if appropriate.
   1993 
   1994 If in Dired, consider FILE to be the one at point, else prompt
   1995 with minibuffer completion for one.
   1996 
   1997 If FILE has a Denote-compliant identifier, retain it while
   1998 updating the TITLE and KEYWORDS fields of the file name.  Else
   1999 create an identifier based on the following conditions:
   2000 
   2001 - If FILE does not have an identifier and optional DATE is
   2002   non-nil (such as with a prefix argument), invoke the function
   2003   `denote-prompt-for-date-return-id'.  It prompts for a date and
   2004   uses it to derive the identifier.
   2005 
   2006 - If FILE does not have an identifier and optional DATE is
   2007   nil (this is the case without a prefix argument), use the file
   2008   attributes to determine the last modified date and format it as
   2009   an identifier.
   2010 
   2011 - As a fallback, derive an identifier from the current time.
   2012 
   2013 The default TITLE is retrieved from a line starting with a title
   2014 field in the file's contents, depending on the given file
   2015 type (e.g. #+title for Org).  Else, the file name is used as a
   2016 default value at the minibuffer prompt.
   2017 
   2018 As a final step after the FILE, TITLE, and KEYWORDS prompts, ask
   2019 for confirmation, showing the difference between old and new file
   2020 names.
   2021 
   2022 The file type extension (like .txt) is read from the underlying
   2023 file and is preserved through the renaming process.  Files that
   2024 have no extension are simply left without one.
   2025 
   2026 Renaming only occurs relative to the current directory.  Files
   2027 are not moved between directories.
   2028 
   2029 If the FILE has Denote-style front matter for the TITLE and
   2030 KEYWORDS, ask to rewrite their values in order to reflect the new
   2031 input (this step always requires confirmation and the underlying
   2032 buffer is not saved, so consider invoking `diff-buffer-with-file'
   2033 to double-check the effect).  The rewrite of the FILE and
   2034 KEYWORDS in the front matter should not affect the rest of the
   2035 block.
   2036 
   2037 If the file doesn't have front matter but is among the supported
   2038 file types (per `denote-file-type'), add front matter at the top
   2039 of it and leave the buffer unsaved for further inspection.
   2040 
   2041 For per-file-type front matter, refer to the variables:
   2042 
   2043 - `denote-org-front-matter'
   2044 - `denote-text-front-matter'
   2045 - `denote-toml-front-matter'
   2046 - `denote-yaml-front-matter'
   2047 
   2048 This command is intended to (i) rename existing Denote notes
   2049 while updating their title and keywords in the front matter, (ii)
   2050 convert existing supported file types to Denote notes, and (ii)
   2051 rename non-note files (e.g. PDF) that can benefit from Denote's
   2052 file-naming scheme.  The latter is a convenience we provide,
   2053 since we already have all the requisite mechanisms in
   2054 place (though Denote does not---and will not---manage such
   2055 files)."
   2056   (interactive
   2057    (let* ((file (denote--rename-dired-file-or-prompt))
   2058           (file-type (denote-filetype-heuristics file)))
   2059      (list
   2060       file
   2061       (denote-title-prompt
   2062        (denote--retrieve-title-or-filename file file-type))
   2063       (denote-keywords-prompt)
   2064       current-prefix-arg)))
   2065   (let* ((dir (file-name-directory file))
   2066          (id (denote-retrieve-or-create-file-identifier file date))
   2067          (extension (file-name-extension file t))
   2068          (file-type (denote-filetype-heuristics file))
   2069          (new-name (denote-format-file-name
   2070                     dir id keywords (denote-sluggify title) extension))
   2071          (max-mini-window-height 0.33)) ; allow minibuffer to be resized
   2072     (when (denote-rename-file-prompt file new-name)
   2073       (denote-rename-file-and-buffer file new-name)
   2074       (denote-update-dired-buffers)
   2075       (when (denote-file-is-writable-and-supported-p new-name)
   2076         (if (denote--edit-front-matter-p new-name file-type)
   2077             (denote--rewrite-front-matter new-name title keywords file-type)
   2078           (denote--add-front-matter new-name title keywords id file-type))))))
   2079 
   2080 ;;;###autoload
   2081 (defun denote-dired-rename-marked-files ()
   2082   "Rename marked files in Dired to Denote file name.
   2083 
   2084 The operation does the following:
   2085 
   2086 - the file's existing file name is retained and becomes the TITLE
   2087   field, per Denote's file-naming scheme;
   2088 
   2089 - the TITLE is sluggified and downcased, per our conventions;
   2090 
   2091 - an identifier is prepended to the TITLE;
   2092 
   2093 - the file's extension is retained;
   2094 
   2095 - a prompt is asked once for the KEYWORDS field and the input is
   2096   applied to all file names;
   2097 
   2098 - if the file is recognized as a Denote note, add a front matter
   2099   or rewrite it to include the new keywords.  A confirmation to
   2100   carry out this step is performed once at the outset.  Note that
   2101   the affected buffers are not saved.  The user can thus check
   2102   them to confirm that the new front matter does not cause any
   2103   problems (e.g. with the command `diff-buffer-with-file').
   2104   Multiple buffers can be saved with `save-some-buffers' (read
   2105   its doc string).  The addition of front matter takes place only
   2106   if the given file has the appropriate file type extension (per
   2107   the user option `denote-file-type')."
   2108   (interactive nil dired-mode)
   2109   (if-let ((marks (dired-get-marked-files)))
   2110       (let ((keywords (denote-keywords-prompt)))
   2111         (when (yes-or-no-p "Add front matter or rewrite front matter of keywords (buffers are not saved)?")
   2112           (progn
   2113             (dolist (file marks)
   2114               (let* ((dir (file-name-directory file))
   2115                      (id (denote-retrieve-or-create-file-identifier file))
   2116                      (file-type (denote-filetype-heuristics file))
   2117                      (title (denote--retrieve-title-or-filename file file-type))
   2118                      (extension (file-name-extension file t))
   2119                      (new-name (denote-format-file-name
   2120                                 dir id keywords (denote-sluggify title) extension)))
   2121                 (denote-rename-file-and-buffer file new-name)
   2122                 (when (denote-file-is-writable-and-supported-p new-name)
   2123                   (if (denote--edit-front-matter-p new-name file-type)
   2124                       (denote--rewrite-keywords new-name keywords file-type)
   2125                     (denote--add-front-matter new-name title keywords id file-type)))))
   2126             (revert-buffer))))
   2127     (user-error "No marked files; aborting")))
   2128 
   2129 ;;;###autoload
   2130 (defun denote-rename-file-using-front-matter (file &optional auto-confirm)
   2131   "Rename FILE using its front matter as input.
   2132 When called interactively, FILE is the return value of the
   2133 function `buffer-file-name' which is subsequently inspected for
   2134 the requisite front matter.  It is thus implied that the FILE has
   2135 a file type that is supported by Denote, per `denote-file-type'.
   2136 
   2137 Unless AUTO-CONFIRM is non-nil (such as with a prefix argument),
   2138 ask for confirmation, showing the difference between the old and
   2139 the new file names.
   2140 
   2141 Never modify the identifier of the FILE, if any, even if it is
   2142 edited in the front matter.  Denote considers the file name to be
   2143 the source of truth in this case to avoid potential breakage with
   2144 typos and the like.
   2145 
   2146 Refrain from performing the operation if the buffer has unsaved
   2147 changes.  Inform the user about the need to save their changes
   2148 first.  If AUTO-CONFIRM is non-nil, then save the buffer and
   2149 proceed with the renaming."
   2150   (interactive (list (buffer-file-name) current-prefix-arg))
   2151   (when (buffer-modified-p)
   2152     (if (or auto-confirm
   2153             (y-or-n-p "Would you like to save the buffer?"))
   2154         (save-buffer)
   2155       (user-error "Save buffer before proceeding")))
   2156   (unless (denote-file-is-writable-and-supported-p file)
   2157     (user-error "The file is not writable or does not have a supported file extension"))
   2158   (if-let* ((file-type (denote-filetype-heuristics file))
   2159             (title (denote-retrieve-title-value file file-type))
   2160             (keywords (denote-retrieve-keywords-value file file-type))
   2161             (extension (file-name-extension file t))
   2162             (id (denote-retrieve-or-create-file-identifier file))
   2163             (dir (file-name-directory file))
   2164             (new-name (denote-format-file-name
   2165                        dir id keywords (denote-sluggify title) extension)))
   2166       (when (or auto-confirm
   2167                 (denote-rename-file-prompt file new-name))
   2168         (denote-rename-file-and-buffer file new-name)
   2169         (denote-update-dired-buffers))
   2170     (user-error "No front matter for title and/or keywords")))
   2171 
   2172 ;;;###autoload
   2173 (defun denote-dired-rename-marked-files-using-front-matter ()
   2174   "Rename marked files in Dired using their front matter as input.
   2175 Marked files must count as notes for the purposes of Denote,
   2176 which means that they at least have an identifier in their file
   2177 name and use a supported file type, per `denote-file-type'.
   2178 Files that do not meet this criterion are ignored.
   2179 
   2180 The operation does the following:
   2181 
   2182 - the title in the front matter becomes the TITLE component of
   2183   the file name, with hyphenation per Denote's file-naming
   2184   scheme;
   2185 
   2186 - the keywords in the front matter are used for the KEYWORDS
   2187   component of the file name and are processed accordingly, if
   2188   needed;
   2189 
   2190 - the identifier remains unchanged in the file name even if it is
   2191   modified in the front matter (this is done to avoid breakage
   2192   caused by typos and the like).
   2193 
   2194 NOTE that files must be saved, because Denote reads from the
   2195 underlying file, not a modified buffer (this is done to avoid
   2196 potential mistakes).  The return value of a modified buffer is
   2197 the one prior to the modification, i.e. the one already written
   2198 on disk.
   2199 
   2200 This command is useful for synchronizing multiple file names with
   2201 their respective front matter."
   2202   (interactive nil dired-mode)
   2203   (if-let ((marks (seq-filter
   2204                    #'denote-file-is-writable-and-supported-p
   2205                    (dired-get-marked-files))))
   2206       (progn
   2207         (dolist (file marks)
   2208           (let* ((dir (file-name-directory file))
   2209                  (id (denote-retrieve-or-create-file-identifier file))
   2210                  (file-type (denote-filetype-heuristics file))
   2211                  (title (denote-retrieve-title-value file file-type))
   2212                  (keywords (denote-retrieve-keywords-value file file-type))
   2213                  (extension (file-name-extension file t))
   2214                  (new-name (denote-format-file-name
   2215                             dir id keywords (denote-sluggify title) extension)))
   2216             (denote-rename-file-and-buffer file new-name)))
   2217         (revert-buffer))
   2218     (user-error "No marked files; aborting")))
   2219 
   2220 ;;;;; Creation of front matter
   2221 
   2222 ;;;###autoload
   2223 (defun denote-add-front-matter (file title keywords)
   2224   "Insert front matter at the top of FILE.
   2225 
   2226 When called interactively, FILE is the return value of the
   2227 function `buffer-file-name'.  FILE is checked to determine
   2228 whether it is a note for Denote's purposes.
   2229 
   2230 TITLE is a string.  Interactively, it is the user input at the
   2231 minibuffer prompt.
   2232 
   2233 KEYWORDS is a list of strings.  Interactively, it is the user
   2234 input at the minibuffer prompt.  This one supports completion for
   2235 multiple entries, each separated by the `crm-separator' (normally
   2236 a comma).
   2237 
   2238 The purpose of this command is to help the user generate new
   2239 front matter for an existing note (perhaps because the user
   2240 deleted the previous one and could not undo the change).
   2241 
   2242 This command does not rename the file (e.g. to update the
   2243 keywords).  To rename a file by reading its front matter as
   2244 input, use `denote-rename-file-using-front-matter'.
   2245 
   2246 Note that this command is useful only for existing Denote notes.
   2247 If the user needs to convert a generic text file to a Denote
   2248 note, they can use one of the command which first rename the file
   2249 to make it comply with our file-naming scheme and then add the
   2250 relevant front matter."
   2251   (interactive
   2252    (list
   2253     (buffer-file-name)
   2254     (denote-title-prompt)
   2255     (denote-keywords-prompt)))
   2256   (when (denote-file-is-writable-and-supported-p file)
   2257     (denote--add-front-matter
   2258      file title keywords
   2259      (denote-retrieve-or-create-file-identifier file)
   2260      (denote-filetype-heuristics file))))
   2261 
   2262 ;;;; The Denote faces
   2263 
   2264 (defgroup denote-faces ()
   2265   "Faces for Denote."
   2266   :group 'denote)
   2267 
   2268 (defface denote-faces-link '((t :inherit link))
   2269   "Face used to style Denote links in the buffer."
   2270   :group 'denote-faces
   2271   :package-version '(denote . "0.5.0"))
   2272 
   2273 (make-obsolete-variable 'denote-faces-broken-link nil "1.0.0")
   2274 
   2275 (defface denote-faces-subdirectory '((t :inherit bold))
   2276   "Face for subdirectory of file name.
   2277 This should only ever needed in the backlinks' buffer (or
   2278 equivalent), not in Dired."
   2279   :group 'denote-faces
   2280   :package-version '(denote . "0.2.0"))
   2281 
   2282 (defface denote-faces-date '((t :inherit font-lock-variable-name-face))
   2283   "Face for file name date in Dired buffers.
   2284 This is the part of the identifier that covers the year, month,
   2285 and day."
   2286   :group 'denote-faces
   2287   :package-version '(denote . "0.1.0"))
   2288 
   2289 (defface denote-faces-time '((t :inherit denote-faces-date))
   2290   "Face for file name time in Dired buffers.
   2291 This is the part of the identifier that covers the hours, minutes,
   2292 and seconds."
   2293   :group 'denote-faces
   2294   :package-version '(denote . "0.1.0"))
   2295 
   2296 (defface denote-faces-title nil
   2297   "Face for file name title in Dired buffers."
   2298   :group 'denote-faces
   2299   :package-version '(denote . "0.1.0"))
   2300 
   2301 (defface denote-faces-extension '((t :inherit shadow))
   2302   "Face for file extension type in Dired buffers."
   2303   :group 'denote-faces
   2304   :package-version '(denote . "0.1.0"))
   2305 
   2306 (defface denote-faces-keywords '((t :inherit font-lock-builtin-face))
   2307   "Face for file name keywords in Dired buffers."
   2308   :group 'denote-faces
   2309   :package-version '(denote . "0.1.0"))
   2310 
   2311 (defface denote-faces-delimiter
   2312   '((((class color) (min-colors 88) (background light))
   2313      :foreground "gray70")
   2314     (((class color) (min-colors 88) (background dark))
   2315      :foreground "gray30")
   2316     (t :inherit shadow))
   2317   "Face for file name delimiters in Dired buffers."
   2318   :group 'denote-faces
   2319   :package-version '(denote . "0.1.0"))
   2320 
   2321 ;; For character classes, evaluate: (info "(elisp) Char Classes")
   2322 (defvar denote-faces--file-name-regexp
   2323   (concat "\\(?1:[0-9]\\{8\\}\\)\\(?2:T[0-9]\\{6\\}\\)"
   2324           "\\(?:\\(?3:--\\)\\(?4:[[:alnum:][:nonascii:]-]*\\)\\)?"
   2325           "\\(?:\\(?5:__\\)\\(?6:[[:alnum:][:nonascii:]_-]*\\)\\)?"
   2326           "\\(?7:\\..*\\)?$")
   2327   "Regexp of file names for fontification.")
   2328 
   2329 (defconst denote-faces-file-name-keywords
   2330   `((,(concat " " denote-faces--file-name-regexp)
   2331      (1 'denote-faces-date)
   2332      (2 'denote-faces-time)
   2333      (3 'denote-faces-delimiter nil t)
   2334      (4 'denote-faces-title nil t)
   2335      (5 'denote-faces-delimiter nil t)
   2336      (6 'denote-faces-keywords nil t)
   2337      (7 'denote-faces-extension nil t )))
   2338   "Keywords for fontification of file names.")
   2339 
   2340 (defconst denote-faces-file-name-keywords-for-backlinks
   2341   `((,(concat "^\\(?8:.*/\\)?" denote-faces--file-name-regexp)
   2342      (8 'denote-faces-subdirectory nil t)
   2343      (1 'denote-faces-date)
   2344      (2 'denote-faces-time)
   2345      (3 'denote-faces-delimiter nil t)
   2346      (4 'denote-faces-title nil t)
   2347      (5 'denote-faces-delimiter nil t)
   2348      (6 'denote-faces-keywords nil t)
   2349      (7 'denote-faces-extension nil t)))
   2350   "Keywords for fontification of file names in the backlinks buffer.")
   2351 
   2352 ;;;; Fontification in Dired
   2353 
   2354 (defgroup denote-dired ()
   2355   "Integration between Denote and Dired."
   2356   :group 'denote)
   2357 
   2358 (defcustom denote-dired-directories
   2359   ;; We use different ways to specify a path for demo purposes.
   2360   (list denote-directory
   2361         ;; (thread-last denote-directory (expand-file-name "attachments"))
   2362         (expand-file-name "~/Documents/vlog"))
   2363   "List of directories where `denote-dired-mode' should apply to."
   2364   :type '(repeat directory)
   2365   :package-version '(denote . "0.1.0")
   2366   :link '(info-link "(denote) Fontification in Dired")
   2367   :group 'denote-dired)
   2368 
   2369 ;; NOTE 2022-09-12: I tried to use the `dired-font-lock-keywords', but
   2370 ;; then it overrides the standard Dired faces.  The `diredfl' package
   2371 ;; uses that method, though it redefines all Dired faces.  We don't want
   2372 ;; to do that.
   2373 
   2374 ;; FIXME 2022-08-12: Make `denote-dired-mode' actually apply to Dired.
   2375 ;; FIXME 2022-08-12: Make `denote-dired-mode' persist after WDired.
   2376 ;; FIXME 2022-08-12: Make `denote-dired-mode' work with diredfl.  This
   2377 ;; may prove challenging.
   2378 
   2379 ;;;###autoload
   2380 (define-minor-mode denote-dired-mode
   2381   "Fontify all Denote-style file names.
   2382 Add this or `denote-dired-mode-in-directories' to
   2383 `dired-mode-hook'."
   2384   :global nil
   2385   :group 'denote-dired
   2386   (if denote-dired-mode
   2387       (font-lock-add-keywords nil denote-faces-file-name-keywords t)
   2388     (font-lock-remove-keywords nil denote-faces-file-name-keywords))
   2389   (font-lock-flush (point-min) (point-max)))
   2390 
   2391 (defun denote-dired--modes-dirs-as-dirs ()
   2392   "Return `denote-dired-directories' as directories.
   2393 The intent is to basically make sure that however a path is
   2394 written, it is always returned as a directory."
   2395   (mapcar
   2396    (lambda (dir)
   2397      (file-name-as-directory (file-truename dir)))
   2398    denote-dired-directories))
   2399 
   2400 ;;;###autoload
   2401 (defun denote-dired-mode-in-directories ()
   2402   "Enable `denote-dired-mode' in `denote-dired-directories'.
   2403 Add this function to `dired-mode-hook'."
   2404   (when (member (file-truename default-directory) (denote-dired--modes-dirs-as-dirs))
   2405     (denote-dired-mode 1)))
   2406 
   2407 ;;;; The linking facility
   2408 
   2409 (defgroup denote-link ()
   2410   "Link facility for Denote."
   2411   :group 'denote)
   2412 
   2413 ;;;;; User options
   2414 
   2415 (defcustom denote-link-backlinks-display-buffer-action
   2416   '((display-buffer-reuse-window display-buffer-below-selected)
   2417     (window-height . fit-window-to-buffer))
   2418   "The action used to display the current file's backlinks buffer.
   2419 
   2420 The value has the form (FUNCTION . ALIST), where FUNCTION is
   2421 either an \"action function\", a list thereof, or possibly an
   2422 empty list.  ALIST is a list of \"action alist\" which may be
   2423 omitted (or be empty).
   2424 
   2425 Sample configuration to display the buffer in a side window on
   2426 the left of the Emacs frame:
   2427 
   2428     (setq denote-link-backlinks-display-buffer-action
   2429           (quote ((display-buffer-reuse-window
   2430                    display-buffer-in-side-window)
   2431                   (side . left)
   2432                   (slot . 99)
   2433                   (window-width . 0.3))))
   2434 
   2435 See Info node `(elisp) Displaying Buffers' for more details
   2436 and/or the documentation string of `display-buffer'."
   2437   :type '(cons (choice (function :tag "Display Function")
   2438                        (repeat :tag "Display Functions" function))
   2439                alist)
   2440   :package-version '(denote . "0.1.0")
   2441   :group 'denote-link)
   2442 
   2443 ;;;;; Link to note
   2444 
   2445 (define-obsolete-variable-alias
   2446   'denote-link--format-org
   2447   'denote-org-link-format
   2448   "1.2.0")
   2449 
   2450 (defvar denote-org-link-format "[[denote:%s][%s]]"
   2451   "Format of Org link to note.
   2452 The value is passed to `format' with IDENTIFIER and TITLE
   2453 arguments, in this order.
   2454 
   2455 Also see `denote-org-link-in-context-regexp'.")
   2456 
   2457 (define-obsolete-variable-alias
   2458   'denote-link--format-markdown
   2459   'denote-md-link-format
   2460   "1.2.0")
   2461 
   2462 (defvar denote-md-link-format "[%2$s](denote:%1$s)"
   2463   "Format of Markdown link to note.
   2464 The %N$s notation used in the default value is for `format' as
   2465 the supplied arguments are IDENTIFIER and TITLE, in this order.
   2466 
   2467 Also see `denote-md-link-in-context-regexp'.")
   2468 
   2469 (define-obsolete-variable-alias
   2470   'denote-link--format-id-only
   2471   'denote-id-only-link-format
   2472   "1.2.0")
   2473 
   2474 (defvar denote-id-only-link-format "[[denote:%s]]"
   2475   "Format of identifier-only link to note.
   2476 The value is passed to `format' with IDENTIFIER as its sole
   2477 argument.
   2478 
   2479 Also see `denote-id-only-link-in-context-regexp'.")
   2480 
   2481 (define-obsolete-variable-alias
   2482   'denote-link--regexp-org
   2483   'denote-org-link-in-context-regexp
   2484   "1.2.0")
   2485 
   2486 (defvar denote-org-link-in-context-regexp
   2487   (concat "\\[\\[" "denote:"  "\\(?1:" denote-id-regexp "\\)" "]" "\\[.*?]]")
   2488   "Regexp to match an Org link in its context.
   2489 The format of such links is `denote-org-link-format'.")
   2490 
   2491 (define-obsolete-variable-alias
   2492   'denote-link--regexp-markdown
   2493   'denote-md-link-in-context-regexp
   2494   "1.2.0")
   2495 
   2496 (defvar denote-md-link-in-context-regexp
   2497   (concat "\\[.*?]" "(denote:"  "\\(?1:" denote-id-regexp "\\)" ")")
   2498   "Regexp to match a Markdown link in its context.
   2499 The format of such links is `denote-md-link-format'.")
   2500 
   2501 (define-obsolete-variable-alias
   2502   'denote-link--regexp-plain
   2503   'denote-id-only-link-in-context-regexp
   2504   "1.2.0")
   2505 
   2506 (defvar denote-id-only-link-in-context-regexp
   2507   (concat "\\[\\[" "denote:"  "\\(?1:" denote-id-regexp "\\)" "]]")
   2508   "Regexp to match an identifier-only link in its context.
   2509 The format of such links is `denote-id-only-link-format'."  )
   2510 
   2511 (defun denote-link--file-type-format (file-type id-only)
   2512   "Return link format based on FILE-TYPE.
   2513 With non-nil ID-ONLY, use the generic link format without a
   2514 title.
   2515 
   2516 Fall back to `denote-org-link-format'."
   2517   ;; Includes backup files.  Maybe we can remove them?
   2518   (cond
   2519    (id-only denote-id-only-link-format)
   2520    ((when-let ((link (denote--link-format file-type)))
   2521       link))
   2522    ;; Plain text also uses [[denote:ID][TITLE]]
   2523    (t denote-org-link-format)))
   2524 
   2525 (defun denote-link--format-link (file format &optional description)
   2526   "Prepare link to FILE using FORMAT.
   2527 If DESCRIPTION is non-nil, use it as link description instead of
   2528 FILE's title.
   2529 
   2530 FORMAT is the symbol of a variable that specifies a string.  See
   2531 the `:link' property of `denote-file-types'."
   2532   (let* ((file-id (denote-retrieve-filename-identifier file))
   2533          (fm (if (symbolp format) (symbol-value format) format))
   2534          (file-type (denote-filetype-heuristics file))
   2535          (file-title (unless (string= fm denote-id-only-link-format)
   2536                        (or description (denote--retrieve-title-or-filename file file-type)))))
   2537     (format fm file-id file-title)))
   2538 
   2539 ;;;###autoload
   2540 (defun denote-link (target &optional id-only)
   2541   "Create link to TARGET note in variable `denote-directory'.
   2542 With optional ID-ONLY, such as a universal prefix
   2543 argument (\\[universal-argument]), insert links with just the
   2544 identifier and no further description.  In this case, the link
   2545 format is always [[denote:IDENTIFIER]].
   2546 
   2547 Use TARGET's title for the link's description.  The title comes
   2548 either from the front matter or the file name.
   2549 
   2550 If region is active, use its text as the link's description
   2551 instead of TARGET's title.  If active region is empty (i.e
   2552 whitespace-only), insert an ID-ONLY link."
   2553   (interactive (list (denote-file-prompt) current-prefix-arg))
   2554   (let* ((beg (point))
   2555          (description (when-let* (((region-active-p))
   2556                                   (beg (region-beginning))
   2557                                   (end (region-end))
   2558                                   (selected-text
   2559                                    (string-trim
   2560                                     (buffer-substring-no-properties beg end))))
   2561                         (delete-region beg end)
   2562                         selected-text))
   2563          (identifier-only (or id-only (string-empty-p description)))
   2564          (file-type (denote-filetype-heuristics (buffer-file-name))))
   2565     (insert
   2566      (denote-link--format-link
   2567       target
   2568       (denote-link--file-type-format file-type identifier-only)
   2569       description))
   2570     (unless (derived-mode-p 'org-mode)
   2571       (make-button beg (point) 'type 'denote-link-button))))
   2572 
   2573 (defalias 'denote-link-insert-link (symbol-function 'denote-link))
   2574 
   2575 (defun denote-link--collect-identifiers (regexp)
   2576   "Return collection of identifiers in buffer matching REGEXP."
   2577   (let (matches)
   2578     (save-excursion
   2579       (goto-char (point-min))
   2580       (while (or (re-search-forward regexp nil t)
   2581                  (re-search-forward denote-id-only-link-in-context-regexp nil t))
   2582         (push (match-string-no-properties 1) matches)))
   2583     matches))
   2584 
   2585 (defun denote-link--expand-identifiers (regexp)
   2586   "Expend identifiers matching REGEXP into file paths."
   2587   (let ((files (denote-directory-files))
   2588         (rx (if (symbolp regexp) (symbol-value regexp) regexp))
   2589         found-files)
   2590     (dolist (file files)
   2591       (dolist (i (denote-link--collect-identifiers rx))
   2592         (when (string-prefix-p i (file-name-nondirectory file))
   2593           (push file found-files))))
   2594     found-files))
   2595 
   2596 (defvar denote-link--find-file-history nil
   2597   "History for `denote-link-find-file'.")
   2598 
   2599 (defun denote-link--find-file-prompt (files)
   2600   "Prompt for linked file among FILES."
   2601   (let ((file-names (mapcar #'denote-get-file-name-relative-to-denote-directory
   2602                             files)))
   2603     (completing-read
   2604      "Find linked file: "
   2605      (denote--completion-table 'file file-names)
   2606      nil t nil 'denote-link--find-file-history)))
   2607 
   2608 ;;;###autoload
   2609 (defun denote-link-find-file ()
   2610   "Use minibuffer completion to visit linked file."
   2611   (interactive)
   2612   (if-let* ((current-file (buffer-file-name))
   2613             (file-type (denote-filetype-heuristics current-file))
   2614             (regexp (denote--link-in-context-regexp file-type))
   2615             (files (denote-link--expand-identifiers regexp)))
   2616       (find-file
   2617        (denote-get-path-by-id
   2618         (denote-extract-id-from-string
   2619          (denote-link--find-file-prompt files))))
   2620     (user-error "No links found in the current buffer")))
   2621 
   2622 ;;;###autoload
   2623 (defun denote-link-find-backlink ()
   2624   "Use minibuffer completion to visit backlink to current file.
   2625 
   2626 Like `denote-link-find-file', but select backlink to follow."
   2627   (interactive)
   2628   (if-let* ((file (buffer-file-name))
   2629             (id (denote-retrieve-filename-identifier file))
   2630             (files (delete file (denote--retrieve-files-in-xrefs id))))
   2631       (find-file
   2632        (denote-get-path-by-id
   2633         (denote-extract-id-from-string
   2634          (denote-link--find-file-prompt files))))
   2635     (user-error "No links found in the current buffer")))
   2636 
   2637 ;;;###autoload
   2638 (defun denote-link-after-creating (&optional id-only)
   2639   "Create new note in the background and link to it directly.
   2640 
   2641 Use `denote' interactively to produce the new note.  Its doc
   2642 string explains which prompts will be used and under what
   2643 conditions.
   2644 
   2645 With optional ID-ONLY as a prefix argument create a link that
   2646 consists of just the identifier.  Else try to also include the
   2647 file's title.  This has the same meaning as in `denote-link'.
   2648 
   2649 IMPORTANT NOTE: Normally, `denote' does not save the buffer it
   2650 produces for the new note.  This is a safety precaution to not
   2651 write to disk unless the user wants it (e.g. the user may choose
   2652 to kill the buffer, thus cancelling the creation of the note).
   2653 However, for this command the creation of the note happens in the
   2654 background and the user may miss the step of saving their buffer.
   2655 We thus have to save the buffer in order to (i) establish valid
   2656 links, and (ii) retrieve whatever front matter from the target
   2657 file."
   2658   (interactive "P")
   2659   (let (path)
   2660     (save-window-excursion
   2661       (call-interactively #'denote)
   2662       (save-buffer)
   2663       (setq path (buffer-file-name)))
   2664     (denote-link path id-only)))
   2665 
   2666 ;;;###autoload
   2667 (defun denote-link-or-create (target &optional id-only)
   2668   "Use `denote-link' on TARGET file, creating it if necessary.
   2669 
   2670 If TARGET file does not exist, call `denote-link-after-creating'
   2671 which runs the `denote' command interactively to create the file.
   2672 The established link will then be targeting that new file.
   2673 
   2674 If TARGET file does not exist, add the user input that was used
   2675 to search for it to the minibuffer history of the
   2676 `denote-title-prompt'.  The user can then retrieve and possibly
   2677 further edit their last input, using it as the newly created
   2678 note's actual title.  At the `denote-title-prompt' type
   2679 \\<minibuffer-local-map>\\[previous-history-element].
   2680 
   2681 With optional ID-ONLY as a prefix argument create a link that
   2682 consists of just the identifier.  Else try to also include the
   2683 file's title.  This has the same meaning as in `denote-link'."
   2684   (interactive (list (denote-file-prompt) current-prefix-arg))
   2685   (if (file-exists-p target)
   2686       (denote-link target id-only)
   2687     (call-interactively #'denote-link-after-creating)))
   2688 
   2689 (defalias 'denote-link-to-existing-or-new-note (symbol-function 'denote-link-or-create))
   2690 
   2691 ;;;;; Link buttons
   2692 
   2693 ;; Evaluate: (info "(elisp) Button Properties")
   2694 ;;
   2695 ;; Button can provide a help-echo function as well, but I think we might
   2696 ;; not need it.
   2697 (define-button-type 'denote-link-button
   2698   'follow-link t
   2699   'face 'denote-faces-link
   2700   'action #'denote-link--find-file-at-button)
   2701 
   2702 (autoload 'thing-at-point-looking-at "thingatpt")
   2703 
   2704 (defun denote-link--link-at-point-string ()
   2705   "Return identifier at point."
   2706   (when (or (thing-at-point-looking-at denote-id-only-link-in-context-regexp)
   2707             (thing-at-point-looking-at denote-md-link-in-context-regexp)
   2708             (thing-at-point-looking-at denote-org-link-in-context-regexp)
   2709             ;; Meant to handle the case where a link is broken by
   2710             ;; `fill-paragraph' into two lines, in which case it
   2711             ;; buttonizes only the "denote:ID" part.  Example:
   2712             ;;
   2713             ;; [[denote:20220619T175212][This is a
   2714             ;; test]]
   2715             ;;
   2716             ;; Maybe there is a better way?
   2717             (thing-at-point-looking-at "\\[\\(denote:.*\\)]"))
   2718     (match-string-no-properties 0)))
   2719 
   2720 ;; NOTE 2022-06-15: I add this as a variable for advanced users who may
   2721 ;; prefer something else.  If there is demand for it, we can make it a
   2722 ;; defcustom, but I think it would be premature at this stage.
   2723 (defvar denote-link-button-action #'find-file-other-window
   2724   "Display buffer action for Denote buttons.")
   2725 
   2726 (defun denote-link--find-file-at-button (button)
   2727   "Visit file referenced by BUTTON."
   2728   (let* ((id (denote-extract-id-from-string
   2729               (buffer-substring-no-properties
   2730                (button-start button)
   2731                (button-end button))))
   2732          (file (denote-get-path-by-id id)))
   2733     (funcall denote-link-button-action file)))
   2734 
   2735 ;;;###autoload
   2736 (defun denote-link-buttonize-buffer (&optional beg end)
   2737   "Make denote: links actionable buttons in the current buffer.
   2738 
   2739 Buttonization applies to the plain text and Markdown file types,
   2740 per the user option `denote-file-types'.  It will not do anything
   2741 in `org-mode' buffers, as buttons already work there.  If you do
   2742 not use Markdown or plain text, then you do not need this.
   2743 
   2744 Links work when they point to a file inside the variable
   2745 `denote-directory'.
   2746 
   2747 To buttonize links automatically add this function to the
   2748 `find-file-hook'.  Or call it interactively for on-demand
   2749 buttonization.
   2750 
   2751 When called from Lisp, with optional BEG and END as buffer
   2752 positions, limit the process to the region in-between."
   2753   (interactive)
   2754   (when (and (not (derived-mode-p 'org-mode))
   2755              (denote-file-has-identifier-p (buffer-file-name)))
   2756     (save-excursion
   2757       (goto-char (or beg (point-min)))
   2758       (while (re-search-forward denote-id-regexp end t)
   2759         (when-let ((string (denote-link--link-at-point-string))
   2760                    (beg (match-beginning 0))
   2761                    (end (match-end 0)))
   2762           (make-button beg end 'type 'denote-link-button))))))
   2763 
   2764 ;;;;; Backlinks' buffer
   2765 
   2766 (define-button-type 'denote-link-backlink-button
   2767   'follow-link t
   2768   'action #'denote-link--backlink-find-file
   2769   'face nil)            ; we use this face though we style it later
   2770 
   2771 (defun denote-link--backlink-find-file (button)
   2772   "Action for BUTTON to `find-file'."
   2773   (funcall denote-link-button-action (buffer-substring (button-start button) (button-end button))))
   2774 
   2775 (defun denote-link--display-buffer (buf)
   2776   "Run `display-buffer' on BUF.
   2777 Expand `denote-link-backlinks-display-buffer-action'."
   2778   (display-buffer
   2779    buf
   2780    `(,@denote-link-backlinks-display-buffer-action)))
   2781 
   2782 (defun denote-backlinks-next (&optional n)
   2783   "Use appropriate command for forward motion in backlinks buffer.
   2784 With optional N as a numeric argument, move to the Nth button
   2785 from point (relevant when `denote-backlinks-show-context' is
   2786 nil)."
   2787   (interactive "p" denote-backlinks-mode)
   2788   (if denote-backlinks-show-context
   2789       (xref-next-line)
   2790     (forward-button n)))
   2791 
   2792 (defun denote-backlinks-prev (&optional n)
   2793   "Use appropriate command for backward motion in backlinks buffer.
   2794 With optional N as a numeric argument, move to the Nth button
   2795 from point (relevant when `denote-backlinks-show-context' is
   2796 nil)."
   2797   (interactive "p" denote-backlinks-mode)
   2798   (if denote-backlinks-show-context
   2799       (xref-prev-line)
   2800     (backward-button n)))
   2801 
   2802 (defvar denote-backlinks-mode-map
   2803   (let ((m (make-sparse-keymap)))
   2804     (define-key m "n" #'denote-backlinks-next)
   2805     (define-key m "p" #'denote-backlinks-prev)
   2806     (define-key m "g" #'revert-buffer)
   2807     m)
   2808   "Keymap for `denote-backlinks-mode'.")
   2809 
   2810 (make-obsolete-variable 'denote-backlink-mode-map 'denote-backlinks-mode-map "0.6.0")
   2811 
   2812 (define-derived-mode denote-backlinks-mode xref--xref-buffer-mode "Backlinks"
   2813   "Major mode for backlinks buffers."
   2814   (unless denote-backlinks-show-context
   2815     (font-lock-add-keywords nil denote-faces-file-name-keywords-for-backlinks t))
   2816   (add-hook 'project-find-functions #'denote-project-find nil t))
   2817 
   2818 (make-obsolete-variable 'denote-backlink-mode 'denote-backlinks-mode "0.6.0")
   2819 
   2820 (defun denote-link--prepare-backlinks (fetcher _alist)
   2821   "Create backlinks' buffer for the current note.
   2822 FETCHER is a function that fetches a list of xrefs.  It is called
   2823 with `funcall' with no argument like `xref--fetcher'.
   2824 
   2825 In the case of `denote', `apply-partially' is used to create a
   2826 function that has already applied another function to multiple
   2827 arguments.
   2828 
   2829 ALIST is not used in favour of using
   2830 `denote-link-backlinks-display-buffer-action'."
   2831   (let* ((inhibit-read-only t)
   2832          (file (buffer-file-name))
   2833          (file-type (denote-filetype-heuristics file))
   2834          (id (denote-retrieve-filename-identifier file))
   2835          (buf (format "*denote-backlinks to %s*" id))
   2836          (xref-alist (xref--analyze (funcall fetcher))))
   2837     (with-current-buffer (get-buffer-create buf)
   2838       (setq-local default-directory (denote-directory))
   2839       (erase-buffer)
   2840       (setq overlay-arrow-position nil)
   2841       (denote-backlinks-mode)
   2842       (goto-char (point-min))
   2843       (when-let*  ((title (denote-retrieve-title-value file file-type))
   2844                    (heading (format "Backlinks to %S (%s)" title id))
   2845                    (l (length heading)))
   2846         (insert (format "%s\n%s\n\n" heading (make-string l ?-))))
   2847       (if denote-backlinks-show-context
   2848           (xref--insert-xrefs xref-alist)
   2849         (mapc (lambda (x)
   2850                 (insert (car x))
   2851                 (make-button (line-beginning-position) (line-end-position) :type 'denote-link-backlink-button)
   2852                 (newline))
   2853               xref-alist))
   2854       (goto-char (point-min))
   2855       (setq-local revert-buffer-function
   2856                   (lambda (_ignore-auto _noconfirm)
   2857                     (when-let ((buffer-file-name file))
   2858                       (denote-link--prepare-backlinks
   2859                        (apply-partially #'xref-matches-in-files id
   2860                                         (delete file (denote-directory-text-only-files)))
   2861                        nil)))))
   2862     (denote-link--display-buffer buf)))
   2863 
   2864 ;;;###autoload
   2865 (defun denote-link-backlinks ()
   2866   "Produce a buffer with backlinks to the current note.
   2867 
   2868 The backlinks' buffer shows the file name of the note linking to
   2869 the current note, as well as the context of each link.
   2870 
   2871 File names are fontified by Denote if the user option
   2872 `denote-link-fontify-backlinks' is non-nil.  If this user option
   2873 is nil, the buffer is fontified by Xref.
   2874 
   2875 The placement of the backlinks' buffer is controlled by the user
   2876 option `denote-link-backlinks-display-buffer-action'.  By
   2877 default, it will show up below the current window."
   2878   (interactive)
   2879   (let ((file (buffer-file-name)))
   2880     (when (denote-file-is-writable-and-supported-p file)
   2881       (let* ((id (denote-retrieve-filename-identifier file))
   2882              (xref-show-xrefs-function #'denote-link--prepare-backlinks)
   2883              (project-find-functions #'denote-project-find))
   2884         (xref--show-xrefs
   2885          (apply-partially #'xref-matches-in-files id
   2886                           ;; remove the current buffer file from the
   2887                           ;; backlinks
   2888                           (delete file (denote-directory-text-only-files)))
   2889          nil)))))
   2890 
   2891 (defalias 'denote-link-show-backlinks-buffer (symbol-function 'denote-link-backlinks))
   2892 
   2893 ;;;;; Add links matching regexp
   2894 
   2895 (defvar denote-link--prepare-links-format "- %s\n"
   2896   "Format specifiers for `denote-link-add-links'.")
   2897 
   2898 ;; NOTE 2022-06-16: There is no need to overwhelm the user with options,
   2899 ;; though I expect someone to want to change the sort order.
   2900 (defvar denote-link-add-links-sort nil
   2901   "When t, add REVERSE to `sort-lines' of `denote-link-add-links'.")
   2902 
   2903 (defun denote-link--prepare-links (files current-file id-only)
   2904   "Prepare links to FILES from CURRENT-FILE.
   2905 When ID-ONLY is non-nil, use a generic link format.  See
   2906 `denote-link--file-type-format'."
   2907   (with-temp-buffer
   2908     (mapc (lambda (file)
   2909             (insert
   2910              (format
   2911               denote-link--prepare-links-format
   2912               (denote-link--format-link
   2913                file
   2914                (denote-link--file-type-format current-file id-only)))))
   2915           files)
   2916     (sort-lines denote-link-add-links-sort (point-min) (point-max))
   2917     (buffer-string)))
   2918 
   2919 (defvar denote-link--add-links-history nil
   2920   "Minibuffer history for `denote-link-add-links'.")
   2921 
   2922 ;;;###autoload
   2923 (defun denote-link-add-links (regexp &optional id-only)
   2924   "Insert links to all notes matching REGEXP.
   2925 Use this command to reference multiple files at once.
   2926 Particularly useful for the creation of metanotes (read the
   2927 manual for more on the matter).
   2928 
   2929 Optional ID-ONLY has the same meaning as in `denote-link': it
   2930 inserts links with just the identifier."
   2931   (interactive
   2932    (list
   2933     (read-regexp "Insert links matching REGEX: " nil 'denote-link--add-links-history)
   2934     current-prefix-arg))
   2935   (let* ((current-file (buffer-file-name))
   2936          (file-type (denote-filetype-heuristics current-file)))
   2937     (if-let ((files (delete current-file
   2938                             (denote-directory-files-matching-regexp regexp)))
   2939              (beg (point)))
   2940         (progn
   2941           (insert (denote-link--prepare-links files file-type id-only))
   2942           (denote-link-buttonize-buffer beg (point)))
   2943       (message "No links matching `%s'" regexp))))
   2944 
   2945 (defalias 'denote-link-insert-links-matching-regexp (symbol-function 'denote-link-add-links))
   2946 
   2947 ;;;###autoload
   2948 (defun denote-link-add-missing-links (regexp &optional id-only)
   2949   "Insert missing links to all notes matching REGEXP.
   2950 Similar to `denote-link-add-links' but insert only links not yet
   2951 present in the current buffer.
   2952 
   2953 Optional ID-ONLY has the same meaning as in `denote-link': it
   2954 inserts links with just the identifier."
   2955   (interactive
   2956    (list
   2957     (read-regexp "Insert links matching REGEX: " nil 'denote-link--add-links-history)
   2958     current-prefix-arg))
   2959   (let* ((current-file (buffer-file-name))
   2960          (file-type (denote-filetype-heuristics current-file))
   2961          (current-id (denote--link-in-context-regexp file-type))
   2962          (linked-files (denote-link--expand-identifiers current-id)))
   2963     (if-let* ((found-files (delete current-file
   2964                                    (denote-directory-files-matching-regexp regexp)))
   2965               (final-files (seq-difference found-files linked-files))
   2966               (beg (point)))
   2967         (progn
   2968           (insert (denote-link--prepare-links final-files file-type id-only))
   2969           (denote-link-buttonize-buffer beg (point)))
   2970       (message "No links matching `%s' that aren't yet present in the current buffer" regexp))))
   2971 
   2972 ;;;;; Links from Dired marks
   2973 
   2974 ;; NOTE 2022-07-21: I don't think we need a history for this one.
   2975 (defun denote-link--buffer-prompt (buffers)
   2976   "Select buffer from BUFFERS visiting Denote notes."
   2977   (let ((buffer-file-names (mapcar #'file-name-nondirectory
   2978                                    buffers)))
   2979     (completing-read
   2980      "Select note buffer: "
   2981      (denote--completion-table 'buffer buffer-file-names)
   2982      nil t)))
   2983 
   2984 (defun denote-link--map-over-notes ()
   2985   "Return list of `denote-file-is-note-p' from Dired marked items."
   2986   (seq-filter
   2987    (lambda (f)
   2988      (and (denote-file-is-note-p f)
   2989           (denote--dir-in-denote-directory-p default-directory)))
   2990    (dired-get-marked-files)))
   2991 
   2992 ;;;###autoload
   2993 (defun denote-link-dired-marked-notes (files buffer &optional id-only)
   2994   "Insert Dired marked FILES as links in BUFFER.
   2995 
   2996 FILES are Denote notes, meaning that they have our file-naming
   2997 scheme, are writable/regular files, and use the appropriate file
   2998 type extension (per `denote-file-type').  Furthermore, the marked
   2999 files need to be inside the variable `denote-directory' or one of
   3000 its subdirectories.  No other file is recognised (the list of
   3001 marked files ignores whatever does not count as a note for our
   3002 purposes).
   3003 
   3004 The BUFFER is one which visits a Denote note file.  If there are
   3005 multiple buffers, prompt with completion for one among them.  If
   3006 there isn't one, throw an error.
   3007 
   3008 With optional ID-ONLY as a prefix argument, insert links with
   3009 just the identifier (same principle as with `denote-link').
   3010 
   3011 This command is meant to be used from a Dired buffer."
   3012   (interactive
   3013    (list
   3014     (denote-link--map-over-notes)
   3015     (let ((file-names (denote--buffer-file-names)))
   3016       (find-file
   3017        (cond
   3018         ((null file-names)
   3019          (user-error "No buffers visiting Denote notes"))
   3020         ((eq (length file-names) 1)
   3021          (car file-names))
   3022         (t
   3023          (denote-link--buffer-prompt file-names)))))
   3024     current-prefix-arg)
   3025    dired-mode)
   3026   (if (null files)
   3027       (user-error "No note files to link to")
   3028     (when (y-or-n-p (format "Create links at point in %s?" buffer))
   3029       (with-current-buffer buffer
   3030         (insert (denote-link--prepare-links
   3031                  files
   3032                  (denote-filetype-heuristics (buffer-file-name))
   3033                  id-only))
   3034         (denote-link-buttonize-buffer)))))
   3035 
   3036 ;;;;; Register `denote:' custom Org hyperlink
   3037 
   3038 (declare-function org-link-open-as-file "ol" (path arg))
   3039 
   3040 (defun denote-link--ol-resolve-link-to-target (link &optional path-id)
   3041   "Resolve LINK into the appropriate target.
   3042 With optional PATH-ID return a cons cell consisting of the path
   3043 and the identifier."
   3044   (let* ((search (and (string-match "::\\(.*\\)\\'" link)
   3045                       (match-string 1 link)))
   3046          (id (if (and (stringp search) (not (string-empty-p search)))
   3047                  (substring link 0 (match-beginning 0))
   3048                link))
   3049          (path (denote-get-path-by-id id)))
   3050     (cond
   3051      (path-id
   3052       (cons (format "%s" path) (format "%s" id)))
   3053      ((and (stringp search) (not (string-empty-p search)))
   3054       (concat path "::" search))
   3055      (path))))
   3056 
   3057 ;;;###autoload
   3058 (defun denote-link-ol-follow (link)
   3059   "Find file of type `denote:' matching LINK.
   3060 LINK is the identifier of the note, optionally followed by a
   3061 search option akin to that of standard Org `file:' link types.
   3062 Read Info node `(org) Search Options'.
   3063 
   3064 Uses the function `denote-directory' to establish the path to the
   3065 file."
   3066   (org-link-open-as-file
   3067    (denote-link--ol-resolve-link-to-target link)
   3068    nil))
   3069 
   3070 ;;;###autoload
   3071 (defun denote-link-ol-complete ()
   3072   "Like `denote-link' but for Org integration.
   3073 This lets the user complete a link through the `org-insert-link'
   3074 interface by first selecting the `denote:' hyperlink type."
   3075   (concat
   3076    "denote:"
   3077    (denote-retrieve-filename-identifier (denote-file-prompt))))
   3078 
   3079 (declare-function org-link-store-props "ol.el" (&rest plist))
   3080 (defvar org-store-link-plist)
   3081 
   3082 ;;;###autoload
   3083 (defun denote-link-ol-store ()
   3084   "Handler for `org-store-link' adding support for denote: links."
   3085   (when-let* ((file (buffer-file-name))
   3086               ((denote-file-is-note-p file))
   3087               (file-type (denote-filetype-heuristics file))
   3088               (file-id (denote-retrieve-filename-identifier file))
   3089               (file-title (denote--retrieve-title-or-filename file file-type)))
   3090     (org-link-store-props
   3091      :type "denote"
   3092      :description file-title
   3093      :link (concat "denote:" file-id))
   3094     org-store-link-plist))
   3095 
   3096 ;;;###autoload
   3097 (defun denote-link-ol-export (link description format)
   3098   "Export a `denote:' link from Org files.
   3099 The LINK, DESCRIPTION, and FORMAT are handled by the export
   3100 backend."
   3101   (let* ((path-id (denote-link--ol-resolve-link-to-target link :path-id))
   3102          (path (file-relative-name (car path-id)))
   3103          (p (file-name-sans-extension path))
   3104          (id (cdr path-id))
   3105          (desc (or description (concat "denote:" id))))
   3106     (cond
   3107      ((eq format 'html) (format "<a href=\"%s.html\">%s</a>" p desc))
   3108      ((eq format 'latex) (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc))
   3109      ((eq format 'texinfo) (format "@uref{%s,%s}" path desc))
   3110      ((eq format 'ascii) (format "[%s] <denote:%s>" desc path)) ; NOTE 2022-06-16: May be tweaked further
   3111      ((eq format 'md) (format "[%s](%s.md)" desc p))
   3112      (t path))))
   3113 
   3114 ;; The `eval-after-load' part with the quoted lambda is adapted from
   3115 ;; Elfeed: <https://github.com/skeeto/elfeed/>.
   3116 
   3117 ;;;###autoload
   3118 (eval-after-load 'org
   3119   `(funcall
   3120     ;; The extra quote below is necessary because uncompiled closures
   3121     ;; do not evaluate to themselves. The quote is harmless for
   3122     ;; byte-compiled function objects.
   3123     ',(lambda ()
   3124         (with-no-warnings
   3125           (org-link-set-parameters
   3126            "denote"
   3127            :follow #'denote-link-ol-follow
   3128            :face 'denote-faces-link
   3129            :complete #'denote-link-ol-complete
   3130            :store #'denote-link-ol-store
   3131            :export #'denote-link-ol-export)))))
   3132 
   3133 ;;;; Glue code for org-capture
   3134 
   3135 (defgroup denote-org-capture ()
   3136   "Integration between Denote and Org Capture."
   3137   :group 'denote)
   3138 
   3139 (defcustom denote-org-capture-specifiers "%l\n%i\n%?"
   3140   "String with format specifiers for `org-capture-templates'.
   3141 Check that variable's documentation for the details.
   3142 
   3143 The string can include arbitrary text.  It is appended to new
   3144 notes via the `denote-org-capture' function.  Every new note has
   3145 the standard front matter we define."
   3146   :type 'string
   3147   :package-version '(denote . "0.1.0")
   3148   :group 'denote-org-capture)
   3149 
   3150 (defvar denote-last-path nil "Store last path.")
   3151 
   3152 ;;;###autoload
   3153 (defun denote-org-capture ()
   3154   "Create new note through `org-capture-templates'.
   3155 Use this as a function that returns the path to the new file.
   3156 The file is populated with Denote's front matter.  It can then be
   3157 expanded with the usual specifiers or strings that
   3158 `org-capture-templates' supports.
   3159 
   3160 Note that this function ignores the `denote-file-type': it always
   3161 sets the Org file extension for the created note to ensure that
   3162 the capture process works as intended, especially for the desired
   3163 output of the `denote-org-capture-specifiers' (which can include
   3164 arbitrary text).
   3165 
   3166 Consult the manual for template samples."
   3167   (let* ((title (denote-title-prompt))
   3168          (keywords (denote-keywords-prompt))
   3169          (front-matter (denote--format-front-matter
   3170                         title (denote--date nil 'org) keywords
   3171                         (format-time-string denote-id-format nil) 'org)))
   3172     (setq denote-last-path
   3173           (denote--path title keywords
   3174                         (file-name-as-directory (denote-directory))
   3175                         (format-time-string denote-id-format) 'org))
   3176     (denote--keywords-add-to-history keywords)
   3177     (concat front-matter denote-org-capture-specifiers)))
   3178 
   3179 (defun denote-org-capture-delete-empty-file ()
   3180   "Delete file if capture with `denote-org-capture' is aborted."
   3181   (when-let* ((file denote-last-path)
   3182               ((denote--file-empty-p file)))
   3183     (delete-file denote-last-path)))
   3184 
   3185 (add-hook 'org-capture-after-finalize-hook #'denote-org-capture-delete-empty-file)
   3186 
   3187 (make-obsolete 'denote-migrate-old-org-filetags nil "1.1.0")
   3188 (make-obsolete 'denote-migrate-old-markdown-yaml-tags nil "1.1.0")
   3189 
   3190 ;;;; Denote extension "modules"
   3191 
   3192 (defvar denote-modules-available
   3193       '(project (project-find-functions . denote-project-find)
   3194         xref    (xref-backend-functions . denote--xref-backend)
   3195         ffap    (denote-module-ffap-enable . denote-module-ffap-disable))
   3196       "Denote modules currently built-in with Denote.
   3197 This variable is a plist.  Each module is represented as a pair
   3198 of a property name and its value being a cons cell; thus a module
   3199 is written in either the following forms:
   3200 
   3201     NAME (HOOK . FUNCTION\)
   3202     NAME (FUNCTION . FUNCTION\)
   3203 
   3204 NAME, HOOK, FUNCTION are symbols.
   3205 
   3206 When a HOOK-FUNCTION pair is used, `denote-modules-enable'
   3207 function will add FUNCTION to HOOK and `denote-modules-disable'
   3208 function will remove FUNCTION from HOOK.  Generally, it should be
   3209 possible to set HOOK-FUNCTION modules locally.
   3210 
   3211 When a FUNCTION-FUNCTION pair is used, the first FUNCTION must be
   3212 an enable function and the second, its corresponding disable
   3213 function to undo the former.  They are both called with no
   3214 arguments.  For FUNCTION-FUNCTION modules, in some cases, it may
   3215 not be possible to enable a module locally.  In these cases, some
   3216 parts of a module may be enabled globally even when local minor
   3217 mode function `denote-modules-mode' is called.
   3218 
   3219 NOTES for future development to add new modules:
   3220 
   3221 It is important that FUNCTION must be defined and loaded before
   3222 `denote-modules-enable' and `denote-moduel-disable' (the new
   3223 functions probably should be written in the source code lines
   3224 before these enable/disable functions)")
   3225 
   3226 (defvar denote-module-ffap-last-enabled nil
   3227   "Value of `ffap-next-regexp' beofe ffap module was last enabled.
   3228 It is used by `denote-module-ffap-disable' to undo the value
   3229 the module previoulsy set.")
   3230 
   3231 (defvar denote-modules-last-enabled nil
   3232   "Denote modules set last time.
   3233 It is used by `denote-modules-enable' and
   3234 `denote-moduules-disable' to undo the modules enabled last time.")
   3235 
   3236 ;; defvars to placate the compilers
   3237 (defvar denote-modules)
   3238 (defvar ffap-next-regexp)
   3239 (defvar ffap-alist)
   3240 
   3241 (defun denote-module-ffap-disable (&optional local)
   3242   "Disable Denote integration with `ffap'.
   3243 This function is meant to be set as a pair function with
   3244 `denote-module-ffap-enable' in `denote-modules-available'.
   3245 
   3246 When LOCAL is non-nil, enable only for the local buffer as
   3247 much as possible.  Currently, `ffap-alist' is only disabled
   3248 globally."
   3249   (require 'ffap)
   3250   (setq ffap-alist (rassq-delete-all  #'denote-get-relative-path-by-id ffap-alist))
   3251   (if local
   3252       (when denote-module-ffap-last-enabled
   3253         (setq-local ffap-next-regexp denote-module-ffap-last-enabled))
   3254     ;; Reset `ffap-next-regexp' only when there is last-active.  Nil
   3255     ;; means it is in the loading process of denote
   3256     (when denote-module-ffap-last-enabled
   3257       (setq ffap-next-regexp denote-module-ffap-last-enabled))))
   3258 
   3259 (defun denote-module-ffap-enable (&optional local)
   3260   "Enable Denote integration with `ffap'.
   3261 This function is meant to be set as a pair function with
   3262 `denote-module-ffap-disable' in `denote-modules-available'.
   3263 
   3264 When LOCAL is non-nil, enable only for the local buffer as much
   3265 as possible.  Currently, `'ffap-alist' is only enabled globally."
   3266   (require 'ffap)
   3267   (if local (setq-local denote-module-ffap-last-active ffap-next-regexp)
   3268     (setq denote-module-ffap-last-enabled ffap-next-regexp)
   3269     (add-to-list 'ffap-alist (cons denote-id-regexp #'denote-get-relative-path-by-id)))
   3270   (if local
   3271       (setq-local ffap-next-regexp (concat ffap-next-regexp "\\|" denote-id-regexp))
   3272     (setq ffap-next-regexp (concat ffap-next-regexp "\\|" denote-id-regexp))))
   3273 
   3274 (defun denote-modules-disable (modules &optional local)
   3275   "Disable Denote integration MODULES.
   3276 This function is meant to be used by `denote-modules-enable',
   3277 which calls this function, passgin `denote-modules-last-enable'
   3278 as MODULES to undo the modules currently active.
   3279 
   3280 When LOCAL is non-nil, disable MODULES locally, where possible.
   3281 
   3282 Refer to document string of `denote-modules-available'."
   3283   (dolist (module modules)
   3284     (let* ((module-def (plist-get denote-modules-available module))
   3285            (hook (car module-def))
   3286            (func (cdr module-def)))
   3287       ;; If HOOK is a function, it's a setup function and FUNC is its
   3288       ;; teardown counterpart.
   3289       (if (functionp hook) (funcall func local)
   3290         (remove-hook hook func local)))))
   3291 
   3292 (defun denote-modules-enable (modules &optional local)
   3293   "Enable MODULES set in `denote-modules'.
   3294 When LOCAL is non-nil, it tries to enable them only locally.
   3295 Whether this is possible or not depends on the module in
   3296 question.
   3297 
   3298 Refer to document string of `denote-modules-available'."
   3299   (denote-modules-disable denote-modules-last-enabled)
   3300   (dolist (module modules)
   3301     (let* ((module-def (plist-get denote-modules-available module))
   3302            (hook (car module-def))
   3303            (func (cdr module-def)))
   3304       ;; If HOOK is a function, it's a setup function and FUNC is its
   3305       ;; teardown counterpart.
   3306       (if (functionp hook) (funcall hook local)
   3307         (add-hook hook func nil local))))
   3308   (if local (setq denote-modules-last-enabled modules)
   3309     (setq denote-modules-last-enabled modules)))
   3310 
   3311 ;;;###autoload
   3312 (define-minor-mode denote-modules-mode
   3313   "Enable Denote integration modules locally.
   3314 Set modules to be enabled in `denote-modules' and activate the
   3315 minor mode, either globally or locally.  The selected modules are
   3316 enabled only when the minor mode is active."
   3317   :global nil
   3318   :init-value nil
   3319   (if denote-modules-mode
   3320       (denote-modules-enable denote-modules :local)
   3321     (denote-modules-disable denote-modules-last-enabled :local)))
   3322 
   3323 ;;;###autoload
   3324 (define-minor-mode denote-modules-global-mode
   3325   "Enable Denote integration modules globally.
   3326 Set modules to be enabled in `denote-modules' and activate the
   3327 minor mode, either globally or locally.  The selected modules are
   3328 enabled only when the minor mode is active."
   3329   :global t
   3330   :init-value nil
   3331   (if denote-modules-global-mode
   3332       (denote-modules-enable denote-modules)
   3333     (denote-modules-disable denote-modules-last-enabled)))
   3334 
   3335 (defun denote-modules-set (symbol value)
   3336   "Set SYMBOL and VALUE for `denote-modules' upon customizing.
   3337 Enable the modules set when `denote-modules-mode' or
   3338 `denote-modules-global-mode' is active.  If not, this function
   3339 does not enable them automatically.  Manually call the minor mode
   3340 globally or locally or set it in your configuration.
   3341 
   3342 It is meant to be used `defcustom' of `denote-modules', thus when
   3343 the minor mode is active, changing the modules in the `customize'
   3344 UI will be effective immediately."
   3345   (set symbol value)
   3346   (when (or denote-modules-global-mode denote-modules-mode)
   3347     (denote-modules-enable value)))
   3348 
   3349 (defcustom denote-modules nil
   3350   "User-selected Denote modules.
   3351 The selected modules are a list of NAME (symbols), and each
   3352 module enables integration with another Emacs built-in feature.
   3353 See `denote-modules-available' for the modules currently
   3354 available.  Set this user option as a list of NAME; for example:
   3355 
   3356     (project xref ffap)
   3357 
   3358 When customized in Customize UI, it presents a set of checkboxes,
   3359 each box checked adds NAME of the module to the list.
   3360 
   3361 Modules are automatically enabled only when either
   3362 `denote-modules-mode' or `denote-modules-global-mode' is active.
   3363 If not, setting the modules does not enable or disable them
   3364 automatically.  Manually call the minor mode globally or locally
   3365 or set it in your configuration."
   3366   :group 'denote
   3367   :set #'denote-modules-set
   3368   :package-version '(denote . "1.2.0")
   3369   :type
   3370   '(set (const :tag "Project integration" project)
   3371         (const :tag "Xref integration " xref)
   3372         (const :tag "Integration with find-file-at-point `ffap'" ffap)))
   3373 
   3374 ;;;; project.el integration
   3375 ;;   This is also used by xref integration
   3376 
   3377 (cl-defmethod project-root ((project (head denote)))
   3378   "Denote's implementation of `project-root' method from `project'.
   3379 Return current variable `denote-directory' as the root of the
   3380 current denote PROJECT."
   3381   (cdr project))
   3382 
   3383 (cl-defmethod project-files ((_project (head denote)) &optional _dirs)
   3384   "Denote's implementation of `project-files' method from `project'.
   3385 Return all files that have an identifier for the current denote
   3386 PROJECT.  The return value may thus include file types that are
   3387 not implied by `denote-file-type'.  To limit the return value to
   3388 text files, use the function `denote-directory-text-only-files'."
   3389   (denote-directory-files))
   3390 
   3391 (defun denote-project-find (dir)
   3392   "Return project instance if DIR is part of variable `denote-directory'.
   3393 The format of project instance is aligned with `project-try-vc'
   3394 defined in `project'."
   3395   (let ((dir (expand-file-name dir)) ; canonicalize current directory name
   3396         (root (denote-directory)))
   3397     (when (or (file-equal-p dir root) ; currently at `denote-directory'
   3398               (string-prefix-p root dir)) ; or its subdirectory
   3399       (cons 'denote root))))
   3400 
   3401 ;;;; Xref integration
   3402 ;;   Set `xref-backend-functions' like this.
   3403 ;;     (add-hook 'xref-backend-functions #'denote--xref-backend)
   3404 ;;
   3405 ;;   You can tell xref-references not to prompt by adding the following:
   3406 ;;     (add-to-list 'xref-prompt-for-identifier #'xref-find-references
   3407 ;;     :append)
   3408 
   3409 (defun denote--xref-backend ()
   3410   "Return denote if `default-directory' is in denote directory."
   3411   (when (denote--dir-in-denote-directory-p default-directory)
   3412     'denote))
   3413 
   3414 (cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'denote)))
   3415   "Return the \"thing\" at point.
   3416 The same logic as `elisp-mode'.  The \"thing\" is assumed to be a
   3417 Denote identifier, but can be any word.  The method checks this
   3418 and errors and if the word at point is not a Denote identifier."
   3419   (let ((bounds (bounds-of-thing-at-point 'word)))
   3420     (and bounds
   3421          (let ((id (buffer-substring-no-properties
   3422                     (car bounds) (cdr bounds))))
   3423            (if (string-match-p denote-id-regexp id)
   3424                ;; Use a property to transport the location of the identifier.
   3425                (propertize id 'pos (car bounds))
   3426              (user-error "%s is not a Denote identifier" id))))))
   3427 
   3428 (cl-defmethod xref-backend-definitions ((_backend (eql 'denote)) identifier)
   3429   "Return xref for the note IDENTIFIER points to."
   3430   (let ((file (denote-get-path-by-id identifier)))
   3431     (when file
   3432       (if (file-equal-p file (buffer-file-name (current-buffer)))
   3433           (user-error "Identifier points to the current buffer")
   3434         ;; Without the message, Xref will report that the ID does not
   3435         ;; exist, which is incorrect in this case.
   3436         (list (xref-make nil (xref-make-file-location file 0 0)))))))
   3437 
   3438 (cl-defmethod xref-backend-references ((_backend (eql 'denote)) identifier)
   3439   "Return list of xrefs where IDENTIFIER is referenced.
   3440 This include the definition itself."
   3441   (xref-matches-in-files identifier (denote-directory-text-only-files)))
   3442 
   3443 (cl-defmethod xref-backend-identifier-completion-table ((_backend
   3444                                                          (eql 'denote)))
   3445   "Return list of Denote identifers as completion table."
   3446 
   3447   (let* ((project-find-functions #'denote-project-find)
   3448          (project (project-current nil (denote-directory)))
   3449          (dirs (list (project-root project)))
   3450          (all-files (project-files project dirs)))
   3451     (mapcar #'denote-retrieve-filename-identifier all-files)))
   3452 
   3453 (provide 'denote)
   3454 ;;; denote.el ends here