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