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