commit 984e27bda33195f6f907a564679017b5a3ec9388 from: Lukas Henkel date: Mon Sep 01 19:21:41 2025 UTC Add denote commit - 2b7a791c1f2ee8221fd8c891bb137bdb0a7ae588 commit + 984e27bda33195f6f907a564679017b5a3ec9388 blob - /dev/null blob + 1f7c4b790168032cdf15e155f3d3f3419b908ce0 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/.dir-locals.el @@ -0,0 +1,4 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((emacs-lisp-mode . ((indent-tabs-mode . nil)))) blob - /dev/null blob + 4f545859df79e2432e5f20cc98e1f5a13b2791f2 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/CHANGELOG.org @@ -0,0 +1,6099 @@ +#+title: Change log of Denote +#+author: Protesilaos Stavrou +#+email: info@protesilaos.com +#+language: en +#+options: ':t toc:nil author:t email:t num:t +#+startup: content + +This document contains the release notes for each tagged commit on the +project's main git repository: . + +The newest release is at the top. For further details, please consult +the manual: . + +#+toc: headlines 1 insert TOC here, with one headline level + +* Version 4.0.0 on 2025-04-15 +:PROPERTIES: +:CUSTOM_ID: h:8a134846-72cc-4fbf-830d-6ca9fd0f9ec8 +:END: + +This is a massive release. There is one breaking change, which should +be easy to adapt to: this pertains to the reorganisation of the +project to separate the "core" of Denote from its "extensions". The +core is the ~denote~ package. Each extension now has its own package +(details below). + +Other than that, this version includes lots of new features for +searching and linking as well as quality-of-life refinements. We have +generalised the infrastructure for performing queries in the +~denote-directory~ and made the buffers with the search results more +useful. + +Take your time to read through this publication. I am writing it for +you. Also remember that the most up-to-date resource for anything +related to Denote is its manual. You are always welcome to contact me: +. Or join the development on the Git +repository. + +As usual, special thanks to Jean-Philippe Gagné Guay for making high +quality contributions to Denote since the beginning of the project ~3 +years ago. Those will not always be headline features, but are +important improvements to the underlying code base. + +I mention contributions from Jean-Philippe and others in its context. +Though I do not cover implementation details, otherwise this document +will be the size of a book. This does not mean that they are no +important though. Please consult the Git commit log for all the +technicalities. + +** All the "extras" are in separate packages, including the Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:26ed2af1-60c8-4217-93b3-bbe344e4eb7b +:END: + +In previous versions of Denote, we included some optional extensions +as part of the ~denote~ package. These included the files +=denote-org-extras.el= (Org dynamic blocks, among others), +=denote-journal-extras.el= (streamlined for journaling), +=denote-silo-extras.el= (working with multiple Denote silos). + +The files =denote-md-extras.el= (Markdown extras) and +=denote-sequence.el= (sequence notes, including Luhmann-style +alphanumeric sequences) were also part of the project during the last +development cycle, though they never made it into a tagged release. + +All these are now available as standalone packages on the official GNU +ELPA archive: + +- ~denote-org~ :: In the Emacs configuration file, replace all + instances of =denote-org-extras= with =denote-org=. + +- ~denote-journal~ :: Replace =denote-journal-extras= with =denote-journal=. + +- ~denote-silo~ :: Replace =denote-silo-extras= with =denote-silo=. + +- ~denote-markdown~ :: Replace =denote-md-extras= with =denote-markdown=. + +- ~denote-sequence~ :: No changes to any of the defined symbols. + Simply get the new package. + +I will document each of these packages further below. The plan, going +forward, is to maintain all the packages and coordinate their new +versions. + +** More things in "core" +:PROPERTIES: +:CUSTOM_ID: h:3820e9cf-f034-4c3c-a4ed-1e7d11f1cd23 +:END: + +While the extras are moved out to their own code repositories, all +other features are merged into =denote.el=. Those include everything +that was in =denote-sort.el= and =denote-rename-buffer.el=. + +- The "sort" mechanism is mostly for package developers. We use it + extensively in our Org dynamic blocks, which are now part of the + ~denote-org~ package. + +- The ~denote-dired~ command (alias ~denote-sort-dired~) is the only + user-facing "sort" command we have always provided. It produces a + fully fledged Dired buffer showing the results of the given search + for file names. The matching files are sorted according to the + user's expressed preference. The details are described in the + manual. + +- The ~denote-rename-buffer-mode~ and all of its user options are + unchanged. This mode automatically renames the buffer of a given + Denote file so that it is easier to read it. Again, the manual + covers the technicalities. + +Users do not need to make changes, unless they are explicitly loading +=denote-sort-dired= and =denote-rename-buffer=. In that case, they may +just remove those calls: only ~denote~ needs to be loaded. + +** The ~denote-query-mode~ +:PROPERTIES: +:CUSTOM_ID: h:36f305e2-310d-4327-941a-ca0570b473d2 +:END: + +Many of the features I will describe below produce search results via +the built-in Xref mechanism. Xref performs a search with a Grep or +Grep-like program, subject to the user option ~xref-search-program~. +The buffer those search results are displayed in runs the +~denote-query-mode~. It supersedes ~denote-backlinks-mode~. + +The ~denote-query-mode~ supports the following: + +- Results are shown in the context, with the exact match in highlight. +- Matches are grouped by file. Each file is a "heading". +- Headings can be folded with =TAB=, just how it is done in Org buffers. +- The results can be used for further queries. Type =C-h m= + (~describe-mode~) to learn about all the relevant commands. + +We have had support for Xref since the original version of Denote. It +now is more generalised to cover backlinks, query links, and +~denote-grep~ (more below). + +** Use query links for file contents or file names +:PROPERTIES: +:CUSTOM_ID: h:c217a37a-db73-46bd-ab5f-3f9c54f9d53b +:END: + +Denote has always provided the option to link directly to a file with +a given name by referencing its identifier. This can be done with the +command ~denote-link~, among a few others like it (always consult the +manual of Denote). + +In addition to these "direct links", we also support "query links". +Those do not point to a file but instead trigger a search. The results +are placed in a buffer that uses the appropriate major mode. + +There are two types of query links: + +- Query file contents :: Use the command ~denote-query-contents-link~ + to insert a query link at point for "file contents". It perform a + search inside files in the ~denote-directory~ and put the results in + a ~denote-query-mode~ buffer. + +- Query file names :: Use the ~denote-query-filenames-link~ to insert + a query link for "file names". It performs the query against file + names (not contents!) and puts the results in a ~dired~ buffer. + +The display of the buffer with the query link results is controlled by +the user option ~denote-query-links-display-buffer-action~. + +Query links are styled a little bit differently than direct links. +Compare the ~denote-faces-link~ with ~denote-faces-query-link~. Both +should look okay with most themes. + +Denote query links are supported as part of the =denote:= hyperlink +type. They are available in all file types we define (per the user +option ~denote-file-type~) and should, in principle, work in any +custom file type (advanced users can check the variable ~denote-file-types~). + +** Backlinks now always show their context +:PROPERTIES: +:CUSTOM_ID: h:8ebc6ae8-1087-46fa-a0ec-464749f0ac4d +:END: + +In the past, the command ~denote-backlinks~ would produce a bespoke +buffer showing a list of file names that included links to the current +file (any file with the Denote file-naming scheme can have backlinks, +by the way, including PDFs, videos, etc.). This buffer did not provide +any additional functionality. We used to support the option to show +results in their context via ~denote-backlinks-show-context~. Those +would be rendered in a standard Xref buffer. + +The contextual results are now the default and sole option. This is +because we have expanded the functionality of those buffers to use the +~denote-query-mode~, as explained above. Plus, it makes our code base +simpler. + +Users will notice how backlikns look just like a query link for file +contents. This is because backlinks are the original query links since +day one of Denote. + +** Direct links to a file with matching contents +:PROPERTIES: +:CUSTOM_ID: h:a1a7b766-328d-4883-93b2-c68b49bd1aa3 +:END: + +The command ~denote-link-to-file-with-contents~ allows users to +produce a direct link to a file whose contents (not file name!) +includes the given query. + +Similarly, the command ~denote-link-to-all-files-with-contents~ +generates a typographic list (bullet list) to all files whose contents +match the given query. + +The manual covers all linking commands in depth. + +** The essence of ~denote-search~ is part of ~denote~ +:PROPERTIES: +:CUSTOM_ID: h:5d2ac378-304c-4ea8-bbfb-b3f7b649b27d +:END: + +The ~denote-search~ package by Lucas Quintana uses the infrastructure +of Denote to perform searches in file contents. We now provide its +feature set as part of core ~denote~. + +We decided to do this since query links already introduced all of the +requisite generalisations to ~denote-query-mode~. + +Users can rely on the commands ~denote-grep~, ~denote-grep-marked-dired-files~, +and ~denote-grep-files-referenced-in-region~. + +The placement of these buffers is subject to the user option +~denote-grep-display-buffer-action~. + +This functionality was introduced in two pull requests by Lucas +Quintana, 571 and 573, with further changes by me: + +- . +- . + +Lucas has assigned copyright to the Free Software Foundation. + +I think this was a much-needed addition to the core of Denote. It +complements ~denote-dired~ and query links. + +** Formatting of links with ~denote-link-description-format~ +:PROPERTIES: +:CUSTOM_ID: h:635b7f04-891a-4a8b-b420-4e0d9dadc232 +:END: + +The old user option ~denote-link-description-function~ is deprecated +and superseded by the new ~denote-link-description-format~. The new +user option still accepts a custom function as its value, so the old +behaviour should be retained. + +What the new ~denote-link-description-format~ supports is an easier +way to customise the description of a link by using format specifiers +for common options. For example, users who only want to see the title +of the linked file can do this: + +#+begin_src emacs-lisp +(setq denote-link-description-format "%t") +#+end_src + +The documentation of this user option covers all the format specifiers +and further details. + +** Miscellaneous changes for all users +:PROPERTIES: +:CUSTOM_ID: h:76a56ab5-c44b-4c67-8048-25dd0dd88dcf +:END: + +- The command ~denote-add-front-matter~ is superseded by + ~denote-rename-file~ and related. Those renaming commands will add + missing front matter or rewrite the modified lines of existing front + matter. This is due to refinements made by Jean-Philippe Gagné Guay + to the file renaming mechanism. We discussed this deprecation in + issue 498: . Also + thanks to Samuel Flint for reporting an earlier problem with file + name signatures: . + +- The user option ~denote-open-link-function~ specifies the function + used by Denote to open the file of a direct link. + +- The user option ~denote-org-store-link-to-heading~ can now be set to + form generic context links without a =PROPERTIES= drawer and + corresponding =CUSTOM_ID=. Set the value of this variable to + ='context=. Read its documentation for further details. + +- Also about ~denote-org-store-link-to-heading~, we have changed its + default value to ~nil~, which is what we were doing for most of + Denote's history. This means that, by default, ~org-store-link~ and + anything building on top of it will create a link only to the + current Denote file, like =denote:IDENTIFIER=, but not to the + current heading within that file. To create links to the + file+heading, set the value of this variable to ='id=. + +- The command ~denote-dired-link-marked-notes~ is an alias for + ~denote-link-dired-marked-notes~. + +- The user option ~denote-sort-dired-extra-prompts~ control what + ~denote-dired~ (alias ~denote-sort-dired~) prompts for. It accepts + either a nil value or a list of symbols among ~sort-by-component~, + ~reverse-sort~, and ~exclude-regexp~. The order those symbols appear + in the list is significant, with the leftmost coming first. + +- There is a new ~denote-sort-identifier-comparison-function~ variable + which determines how identifier-based sorting should be done by + default. It complements the existing ~denote-sort-title-comparison-function~, + ~denote-sort-keywords-comparison-function~, ~denote-sort-signature-comparison-function~. + Thanks to Maikol Solís for the contribution in pull request 517: + . The change is + small, meaning that Maikol does not need to assign copyright to the + Free Software Foundation (though I believe the paperwork is done, anyway). + +- Lots of refinements to the doc strings of individual variables + and/or functions as well as the manual. + +- Lots of other contributions to discussions and questions on the Git + repository. Granted, these are not "changes" per se but are part of + the development effort nonetheless. + +- Made ~denote-get-path-by-id~ use ~denote-get-file-extension-sans-encryption~ + instead of ~denote-get-file-extension~. This fixes a bug where the + extension is duplicated if it has an encryption component. Thanks to + eum3l for the patch in pull request 562: . + The change is small, meaning that the author does not need to assign + copyright to the Free Software Foundation. + +- Same as above for ~denote--rename-file~, which was done in pull + request 557: . + +** For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:7eaf43a6-7d62-440e-bf7c-8d9536c7d36e +:END: + +The following have been added or modified. + ++ NEW Function ~denote-file-has-denoted-filename-p~ :: Return non-nil + if =FILE= respects the file-naming scheme of Denote. This tests the + rules of Denote's file-naming scheme. Sluggification is ignored. It + is done by removing all file name components and validating what + remains. Thanks to Jean-Philippe Gagné Guay for the pull request + 515: . + ++ NEW Functions ~denote-infer-keywords-from-files~ :: Return list of + keywords in ~denote-directory-files~. With optional + =FILES-MATCHING-REGEXP=, only extract keywords from the matching + files. Otherwise, do it for all files. Keep any duplicates. Users + who do not want duplicates should refer to the functions + ~denote-keywords~. + ++ MODIFIED Function ~denote-keywords~ :: Returns an appropriate list + of keyword candidates, while accounting for the value of the user + option ~denote-infer-keywords~. It now also accepts the optional + =FILES-MATCHING-REGEXP= parameter. + ++ MODIFIED Function ~denote-directory-files~ :: Returns a list of + absolute file paths in variable ~denote-directory~. It now accepts + the optional =EXCLUDE-REGEXP= parameter. + ++ MODIFIED Function ~denote-format-file-name~ :: Formats a file name. + The way it treats its =ID= parameter has changed. Please read its + doc string. Thanks to Jean-Philippe Gagné Guay for the pull request + 496: . + ++ ALIAS Function ~denote-retrieve-filename-keywords-as-list~ :: This + is a name that is easier to discover than ~denote-extract-keywords-from-path~, + because of the many other functions with the =denote-retrieve-*= prefix. + ++ MODIFIED Function ~denote-retrieve-filename-identifier~ :: Extracts + the identifier from =FILE= name, if present, else returns nil. To + create a new one from a date, refer to the ~denote-get-identifier~ + function. Thanks to Jean-Philippe Gagné Guay for the pull request + 476: . + ++ MODIFIED Function ~denote-get-identifier~ :: Converts =DATE= into a + Denote identifier using ~denote-id-format~. If =DATE= is nil, it + returns an empty string as the identifier. Also by Jean-Philippe in + pull request 476 mentioned right above. + ++ MODIFIED Function ~denote-date-prompt~ :: Prompts for a date, + expecting =YYYY-MM-DD= or that plus =HH:MM= (or even =HH:MM:SS=). + Can also use Org's more advanced date selection utility if the user + option ~denote-date-prompt-use-org-read-date~ is non-nil. It now has + the optional parameters =INITIAL-DATE= and =PROMPT-TEXT=. Thanks to + Jean-Philippe Gagné Guay for the pull request 576: + . + +- NEW Function ~denote-retrieve-groups-xref-query~ :: Accesses the + location of xrefs for =QUERY= and group them per file. Limit the + search to text files. + +- NEW Function ~denote-retrieve-files-xref-query~ :: Returns sorted, + deduplicated file names with matches for =QUERY= in their contents. + Limits the search to text files. + +- NEW Function ~denote-retrieve-xref-alist~ :: Returns xref alist of + files with the location of matches for =QUERY=. With optional + =FILES-MATCHING-REGEXP=, it limits the list of files accordingly + (per ~denote-directory-files~). At all times, it limits the search + to text files. + ++ NEW Function ~denote-prepend-front-matter~ :: Prepend front matter + to =FILE=. The =TITLE=, =KEYWORDS=, =DATE=, =ID=, =SIGNATURE=, and + =FILE-TYPE= are passed from the renaming command and are used to + construct a new front matter block if appropriate. + ++ MODIFIED Function ~denote-rewrite-front-matter~ :: Rewrites front + matter of note after ~denote-rename-file~ (or related). The =FILE=, + =TITLE=, =KEYWORDS=, =SIGNATURE=, =DATE=, =IDENTIFIER=, and + =FILE-TYPE= arguments are given by the renaming command and are used + to construct new front matter values if appropriate. If + ~denote-rename-confirmations~ contains ~rewrite-front-matter~, + prompt to confirm the rewriting of the front matter. Otherwise + produce a ~y-or-n-p~ prompt to that effect. Thanks to + Jean-Philippe Gagné Guay for the pull request 558: + . + +** Denote "extensions" that are not in the ~denote~ package anymore +:PROPERTIES: +:CUSTOM_ID: h:40e030cd-f462-44ce-add9-ab1525359ae6 +:END: + +*** ~denote-journal~ integrates nicely with =M-x calendar= +:PROPERTIES: +:CUSTOM_ID: h:f8ab710d-852f-4d8b-b0f8-9a24c5c83808 +:END: + +The ~calendar~ can now highlight days that have journal entry. It may +also be used as a date picker to view or write a journal entry for +that day. + +- Thanks to Alan Schmitt for reporting an issue with the calendar + integration during development: + . + +- Thanks to Vineet C. Kulkarni for tweaking the identification of the + journal keyword to be more robust: + . + +- Thanks to Honza Pokorny for fixing two small issues with the path + expansion: + + - + - + +Other than that, the package is providing the same functionality as +the discontinued =denote-journal-extras.el=. + +- Manual: . +- GitHub: . + +*** ~denote-org~ is almost the same as the discontinued =denote-org-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:db491fe0-0c96-4c2e-9320-dc2697106e12 +:END: + +The only addition to dynamic blocks the optional =:not-regexp= parameter. +This is a regular expression that can further filter the results of a +search, such that the matching items are removed from the output. + +The official manual of ~denote-org~ covers the technicalities. + +- Manual: . +- GitHub: . + +Also thanks to Elias Storms for fixing a small issue with the "missing +links" Org dynamic block, in pull request 486: + +*** ~denote-silo~ is the same as the discontinued =denote-silo-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:ed7c77f5-9b44-4e76-9ada-80ff0ed6d5f6 +:END: + +I have only made small tweaks to it, but nothing that changes the user +experience. + ++ Manual: ++ GitHub: + +*** ~denote-markdown~ for some Markdown-specific extras +:PROPERTIES: +:CUSTOM_ID: h:e01d236c-fb50-488f-9fb2-15e866fa122a +:END: + +This package provides some convenience functions to better integrate +Markdown with Denote. This is mostly about converting links from one +type to another so that they can work in different applications +(because Markdown does not have a standardised way to define custom +link types). It also defines an "Obsidian" file type which does not +have any front matter but only uses a level 1 heading for the title of +the note. + +The code of ~denote-markdown~ used to be bundled up with the ~denote~ +package before version =4.0.0= of the latter and was available in the +file =denote-md-extras.el=. Users of the old code will need to adapt +their setup to use the ~denote-markdown~ package. This can be done by +replacing all instances of =denote-md-extras= with =denote-markdown= +across their configuration. + ++ Manual: ++ GitHub: + +*** Write sequence notes (or "folgezettel") with ~denote-sequence~ +:PROPERTIES: +:CUSTOM_ID: h:6181df9e-790f-4fcf-8093-cefbba324cb5 +:END: + +Users who want their notes to have an inherent structure can use +~denote-sequence~. The idea is to have thoughts that naturally form +sequences and are named accordingly. The sequence scheme is either +numeric or alphanumeric. The manual of the package explains all the +details. + ++ Manual: ++ GitHub: + +I had a lot of fun developing this comprehensive package during the +winter holidays. + +Thanks to Claudio Migliorelli, Kierin Bell, Mirko Hernandez for +helping me fix some issues during development: + +- . +- . +- . +- . +- . + +** The ~consult-denote~ also gets a small update +:PROPERTIES: +:CUSTOM_ID: h:90a9287d-a7dd-4d65-9214-4be6ebdf5943 +:END: + +This has always been a standalone package. I made the function +~consult-denote-file-prompt~ read the special-purpose variable +~denote-file-prompt-use-files-matching-regexp~. This is related to +commit =e0f1d47= in denote.git, about issue 536 as reported by Alan +Schmitt: . The +variable =denote-file-prompt-use-files-matching-regexp= is meant to be +~let~ bound and is for advanced users or developers. + +** Feature freeze at least until the end of April 2025 +:PROPERTIES: +:CUSTOM_ID: h:8624e698-90cd-429e-a072-b0fa2df76662 +:END: + +I will not develop new features or accept pull request for a couple of +weeks. The idea is to focus on fixing any bug reports. We can then +publish point releases quickly. + +New features can be included after we are confident that the packages +we have are okay. + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:5191b423-6dc5-4ca7-9bcc-39797be5707c +:END: + +This is just an overview of the Git commits, though remember that +there is more that goes into a project, such as the reporting of +inconsistencies, discussion of new ideas, et cetera. Thanks to +everybody involved! Plus, some commits are large while others are +tiny. + +#+begin_src +~/Git/Projects/denote $ git shortlog 3.1.0..4.0.0 --summary --numbered + 470 Protesilaos Stavrou + 90 Jean-Philippe Gagné Guay + 6 Kierin Bell + 4 Alan Schmitt + 3 eum3l + 2 Claudio Migliorelli + 2 Lucas Quintana + 2 grtcdr + 1 Elias Storms + 1 Laurent Gatto + 1 Maikol Solís + 1 Octavian + 1 TomoeMami +#+end_src + +The following are not accurate because they only reflect the changes +after the reorganisation I made. But we have to start from somewhere. + +#+begin_src +~/Git/Projects/denote-journal $ git shortlog --summary --numbered + 54 Protesilaos Stavrou + 2 Honza Pokorny + 1 Vineet C. Kulkarni +#+end_src + +#+begin_src +~/Git/Projects/denote-sequence $ git shortlog --summary --numbered + 22 Protesilaos Stavrou +#+end_src + +#+begin_src +~/Git/Projects/denote-silo $ git shortlog --summary --numbered + 17 Protesilaos Stavrou +#+end_src + +#+begin_src +~/Git/Projects/denote-org $ git shortlog --summary --numbered + 15 Protesilaos Stavrou +#+end_src + +#+begin_src +~/Git/Projects/denote-markdown $ git shortlog --summary --numbered + 11 Protesilaos Stavrou +#+end_src + +* Version 3.1.0 on 2024-09-04 +:PROPERTIES: +:CUSTOM_ID: h:f089ab11-4ad7-4fd9-9bf3-2deb2e070297 +:END: + +Denote is stable and reliable though we keep adding minor refinements +to it. Remember that many---if not all---of these are intended for +experienced users who have developed their own workflow and want to +adapt Denote to its particularities. We may call them "power users". + +New users do not need to know about every single feature. A basic +configuration is enough and is why the original video I did about +Denote (from even before I published version =0.1.0=) is still relevant. +For example: + +#+begin_src emacs-lisp +;; Start with something like this. +(use-package denote + :ensure t + :bind + (("C-c n n" . denote) + ("C-c n r" . denote-rename-file) + ("C-c n i" . denote-link) ; "insert" mnemonic + ("C-c n b" . denote-backlinks)) + :config + (setq denote-directory (expand-file-name "~/Documents/notes/"))) +#+end_src + +And here is the same idea with a little bit more convenience: + +#+begin_src emacs-lisp +;; Another basic setup with a little more to it. +(use-package denote + :ensure t + :hook (dired-mode . denote-dired-mode) + :bind + (("C-c n n" . denote) + ("C-c n r" . denote-rename-file) + ("C-c n l" . denote-link) + ("C-c n b" . denote-backlinks)) + :config + (setq denote-directory (expand-file-name "~/Documents/notes/")) + + ;; Automatically rename Denote buffers when opening them so that + ;; instead of their long file name they have a literal "[D]" + ;; followed by the file's title. Read the doc string of + ;; `denote-rename-buffer-format' for how to modify this. + (denote-rename-buffer-mode 1)) +#+end_src + +** The ~denote-sort-dired~ command is more configurable +:PROPERTIES: +:CUSTOM_ID: h:717765ae-f76f-4b41-96c0-895fe131a83d +:END: + +The ~denote-sort-dired~ command asks for a literal string or regular +expression and then produces a fully fledged Dired listing of matching +files in the ~denote-directory~. Combined with the efficient Denote +file-naming scheme, this is a killer feature to collect your relevant +files in a consolidated view and have the full power of Dired available. + +By default ~denote-sort-dired~ prompts for the file name component to +sort by and then asks whether to reverse the sorting or not. Users who +want a more streamlined experience can configure the user option +~denote-sort-dired-extra-prompts~. + +It is possible to skip the prompts altogether and use the default +values for (i) which component to sort by and (ii) whether to reverse +the sort. To this end, users can have something like this in their +configuration: + +#+begin_src emacs-lisp +;; Do not issue any extra prompts. Always sort by the `title' file +;; name component and never do a reverse sort. +(setq denote-sort-dired-extra-prompts nil) +(setq denote-sort-dired-default-sort-component 'title) +(setq denote-sort-dired-default-reverse-sort nil) +#+end_src + +For me, Dired is one of the best things about Emacs and I like how it +combines so nicely with Denote file names (this is the cornerstone of +Denote, after all). + +** The ~denote-sort-dired~ sorting functions are customisable +:PROPERTIES: +:CUSTOM_ID: h:7c4824c0-7f9b-46f5-98ea-4ebbab092193 +:END: + +Power users may want to control how the sorting works and what it is +matching on a per file-name-component basis. The user options are +these: + +- ~denote-sort-title-comparison-function~. +- ~denote-sort-keywords-comparison-function~. +- ~denote-sort-signature-comparison-function~. + +One use-case is to match specific patterns inside of file names, such +as Luhmann-style signatures. I wrote about this in the manual as well +as on my blog (with screenshots): +. + +Thanks to Riccardo Giannitrapani for discussing this with me and +helping me understand the use-case better. This was done via a private +channel and I am sharing it with permission. + +** Show the date of each linked file in Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:ad222eb0-06db-4416-820c-c60f31169f66 +:END: + +All our Org dynamic blocks that produce links to files now read the +parameter =:include-date=. When it is set to =t=, the listed files +will include their corresponding date inside of parentheses after the +file's title. + +Thanks to Sergio Rey for describing this idea to me. This was done via +a private channel and the information is shared with permission. + +** Exclude specific directories from Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:5c0b76fc-2758-4a33-875a-fa9eee705d83 +:END: + +The optional Org dynamic blocks we define let users collect links to +other files (and more) in a quick and effective way. Each block +accepts parameters which control its output, such as how to sort +files. + +All our dynamic blocks now accept the =:excluded-dirs-regexp=. This is +a regular expression which is matched against directory file system +paths. Matching directories and their files are not included in the +data handled by the dynamic block. + +Note that we have the user option ~denote-excluded-punctuation-regexp~ +which defines a global preference along the same lines. + +I did a video about this feature: +. + +Thanks to Claudio Migliorelli for discussing this idea with me. It was +done via a private channel and this information is shared with permission. + +** New dynamic block to insert files as headings +:PROPERTIES: +:CUSTOM_ID: h:0cae3fdb-ba83-46f0-9006-13d0073ae092 +:END: + +We already had an Org dynamic block that would insert file contents. +Though that one inserts files as they are, optionally without their +front matter. However, users may have a workflow where they want to +eventually copy some of the block's output into the main file they are +editing, at which point it is easier for the entire inserted file to +appear as a series of headings. The =#+title= of the inserted file +becomes a top-level heading and every other heading is pushed deeper +one level. + +To this end, we provide the Org dynamic block known as ~denote-files-as-headings~. +Insert it with the command ~denote-org-extras-dblock-insert-files-as-headings~ +or select it with the minibuffer after calling Org's own command +~org-dynamic-block-insert-dblock~. + +The top-level headings (those that were the =#+title=) can optionally +link back to the original file. Though please read the manual for all +the parameters this dynamic block takes. + +** The dynamic block for backlinks can be about the current heading only +:PROPERTIES: +:CUSTOM_ID: h:6e6deff6-c02d-4157-85c0-fc405f64ad34 +:END: + +The Org dynamic block for backlinks can now read the optional +=:this-heading-only= parameter. When it is set to =t=, the block will +only include links that point to the specific heading inside of the +current file. Otherwise, backlinks are about the whole file. + +To insert such a dynamic block, use the command +~denote-org-extras-dblock-insert-backlinks~. + +** Toggle the detailed view in backlinks buffers +:PROPERTIES: +:CUSTOM_ID: h:01b4dfb0-3883-4cc8-ac41-8a7b55e42fe8 +:END: + +By default, the buffer produced by the command ~denote-backlinks~ has +a compact view of showing the file names linking to the current file. +With the user option ~denote-backlinks-show-context~ set to a non-nil +value, the backlinks buffer produces a detailed listing of matching +results, where the links are shown in their original context. + +Users can now choose to have this on-demand by calling the command +~denote-backlinks-toggle-context~ which switches between the detailed +and compact views. + +This blog post I wrote about it include screenshots: +. + +** Templates can have a function that returns a string +:PROPERTIES: +:CUSTOM_ID: h:8d0ac1eb-d057-4fce-bf60-98d5de932a0d +:END: + +The ~denote-templates~ variable allows the user to specify one or more +named templates which can then be inserted during the creation of a +new note. One way to be prompted for a template among those specified +is to modify the ~denote-prompts~ user option and then use the regular +~denote~ command. Another way is to use the command ~denote-template~ +(alias ~denote-create-note-with-template~), which will prompt for the +template to use. + +Templates ordinarily have a string as their value, though now their +value can also be the symbol of a function. This function takes no +arguments and is expected to return a string. Denote takes care to +insert that below the front matter of the new note. + +So it can look like this: + +#+begin_src emacs-lisp +(setq denote-templates + `((report . "* Some heading\n\n* Another heading") ; A string with newline characters + (blog . my-denote-template-function-for-blog) ; the symbol of a function that will return a string + (memo . ,(concat "* Some heading" ; expand this `concat' into a string + "\n\n" + "* Another heading" + "\n\n")))) +#+end_src + +Thanks to skissue (Ad) for the contribution in pull request 398: +. The change is small, +meaning that its author does not need to assign copyright to the Free +Software Foundation. + +Also thanks to Jean-Philippe Gagné Guay for extending this to +~denote-org-capture~. Done in pull request 399: +. Jean-Philippe is a +long-time contributor who has assigned copyright to the Free Software +Foundation. + +** The ~denote-rename-buffer-mode~ can now show if a file has backlinks +:PROPERTIES: +:CUSTOM_ID: h:b163982b-3fea-48c8-90a0-d8358e066951 +:END: + +This global minor mode takes care to rename the buffers of Denote +files to a pattern that is easier for users to read. As with +everything, it is highly configurable. The default value now includes +an indicator that shows if the current file has backlinks (other files +linking to it). + +The exact characters used in this indicator are specified in the new +user option ~denote-rename-buffer-backlinks-indicator~. The default +value is ="<-->"=, which hopefully communicates the idea of a link +(but, yeah, symbolism is hard). Users may want to modify this to add +some fancier Unicode character. + +Thanks to Ashton Wiersdorf for the original contribution in pull +request 392: . Ashton +has assigned copyright to the Free Software Foundation. + +** The ~denote-rename-buffer-format~ has changed +:PROPERTIES: +:CUSTOM_ID: h:8913785e-5c87-48ab-9df2-dafbbb9b1a5d +:END: + +In the same theme as above, the user option ~denote-rename-buffer-format~ +has a new default value. Before, it would only show the title of the +file. Now it shows the aforementioned ~denote-rename-buffer-backlinks-indicator~, +if there are backlinks, plus the title, plus a literal ="[D]"= prefix. +The prefix should make it easier to spot Denote files in a buffer +listing. + +Read the documentation of ~denote-rename-buffer-format~ for how to +tweak this to your liking. + +** New user option ~denote-kill-buffers~ +:PROPERTIES: +:CUSTOM_ID: h:14177204-6269-48c1-bc65-de72b59d4e84 +:END: + +This controls whether and when Denote should automatically kill any +buffer it generates while creating a new note or renaming an existing +file. The manual describes the details. + +By default, Denote does not kill any buffers to give users the chance +to review what is on display and confirm any changes or revert them +accordingly. + +Thanks to Jean-Philippe Gagné Guay for the contribution in pull +request 426: . This is +related to issues 273 and 413, so also thanks to Vineet C. Kulkarni +and mentalisttraceur for their participation and/or questions. + +** The ~denote-journal-extras-new-or-existing-entry~ handles any filename component order +:PROPERTIES: +:CUSTOM_ID: h:0b570a97-3e0c-488f-9c9d-02d2f8ac786f +:END: + +Version =3.0.0= of Denote introduced a new option to rearrange the +file name components. All Denote commands should respect it. We did, +however, have a problem with the command ~denote-journal-extras-new-or-existing-entry~ +which was not recognising the date properly. + +Thanks to Jakub Szczerbowski for the contribution in pull request 395: +. The change is small, +meaning that Jakub does not need to assign copyright to the Free +Software Foundation. + +While I am documenting this here, users should already have the fix as +I published a minor release for it in July (in fact, there were 8 +minor releases in the aftermath of the =3.0.0= release, which +addressed several small issues). + +** The ~denote-rename-file-using-front-matter~ recognises the file-at-point in Dired +:PROPERTIES: +:CUSTOM_ID: h:01f652c5-5713-4884-8a01-ee39f1388a12 +:END: + +This makes it consistent with how ~denote-rename-file~ works. I am +implemented this in response to issue 401 where Alp Eren Kose assumed +it was the default behaviour: . + +I think it makes sense to have it this way to avoid such confusion. +Still, it seems easier to edit the file and call ~denote-rename-file-using-front-matter~ +directly, rather do an intermediate step through Dired. + +** The ~denote-rename-file-using-front-matter~ does not ask to rewrite front matter +:PROPERTIES: +:CUSTOM_ID: h:337c5640-8e75-408e-b5cf-4a37ac8e249b +:END: + +The workflow for this command is that the user modifies the front +matter, invokes the command, and Denote takes care to rename the file +accordingly. We had a regression were this would happen as expected, +but Denote would still prompt if it was okay to update the front +matter. That made no sense. + +As with the change mentioned above, this was also fixed in a minor +release so that users would not have to wait all this time. + +** The ~denote-add-links~ and ~denote-find-link~ commands always works inside a silo +:PROPERTIES: +:CUSTOM_ID: h:9c343ddb-a31a-41d5-90c2-ce54622558fa +:END: + +This was always the intended behaviour, though there was an issue with +the implementation that prevented the directory-local value from being +read. + +Thanks to yetanotherfossman for reporting the problem with +~denote-add-links~ in issue 386 and to Kolmas for doing the same for +~denote-find-link~: + +- . +- . + +Also thanks to Jean-Philippe Gagné Guay for following up with a change +to the code that should address the underlying problem with temporary +buffers. This was done in pull request 419: +. + +** Denote commands should work in more special Org buffers +:PROPERTIES: +:CUSTOM_ID: h:e33bea49-f2b4-43ee-96ac-d24630c9bcd7 +:END: + +A case we already handled was ~org-capture~ buffers. Another one is +the buffer produced by the command ~org-tree-to-indirect-buffer~. + +Thanks to coherentstate for bringing this matter to my attention in +issue 418: . + +Also thanks to skissue for noting another edge case that prevented +~denote-rename-buffer-mode~ from doing the right thing. This was +reported in issue 393: . + +** Denote will not create a =CUSTOM_ID= via ~org-capture~ if not necessary +:PROPERTIES: +:CUSTOM_ID: h:c9c04d9a-776d-4b6b-826a-cd9a56f31643 +:END: + +If the ~org-capture~ template does not include one of the specifiers +which produce a link, then we take care to not include a =CUSTOM_ID= +in the properties of the current heading. We do this to make it +possible to link directly to a heading inside of a file (a feature +that is documented in the manual). + +Before, we were creating the =CUSTOM_ID= unconditionally, which was +not the desired behaviour. Thanks to Jonas Großekathöfer for bringing +this matter to my attention in issue 404: +. + +** The prompt for selecting a silo has the appropriate metadata +:PROPERTIES: +:CUSTOM_ID: h:cc5951b1-ee87-4126-b3f2-91c32e3431ae +:END: + +All the Denote minibuffer prompts have the appropriate completion +metadata to integrate with core Emacs functionalities and with +third-party packages that leverage them. One such case pertains to the +completion category our prompts report. This is used by a package such +as ~embark~ to infer the set of relevant actions to perform or by the +~marginalia~ package to produce the appropriate annotations. + +Users will now notice a difference while using commands such as +~denote-silo-extras-create-note~ if they have ~marginalia-mode~ +enabled: all completion candidates will have file-related annotations. + +This is a small change which goes to show how the little things +contribute to a more refined experience. + +** New name for option that controls where backlinks buffers are displayed +:PROPERTIES: +:CUSTOM_ID: h:3e0e2242-8802-4c63-adce-0e20d3f27ad9 +:END: + +The user option is now called ~denote-backlinks-display-buffer-action~. +The old name ~denote-link-backlinks-display-buffer-action~ is an alias +for it and will thus work the same way. Though you are encouraged to +rename it in your configuration as I will eventually remove those +obsolete symbols from the Denote code base. + +** The ~revert-buffer~ should do the right thing in backlinks buffers +:PROPERTIES: +:CUSTOM_ID: h:2982cfcd-6bef-452d-aa0d-d8ab4e721b25 +:END: + +I made several tweaks to the underlying code to ensure that reverting +a backlinks buffer will always reuse the original parameters that +generated it. Backlinks buffers are produced by the ~denote-backlinks~ +command, among others. + +** Lots of new entries in the manual with custom code +:PROPERTIES: +:CUSTOM_ID: h:991d980b-1353-4b53-8ae4-ae09450cce05 +:END: + +The manual of Denote is a rich resource of knowledge for how to use +this package and how to extend it with custom code. I have written the +following entries to further help you improve your productivity: + +- A custom ~denote-region~ that references the source +- Custom sluggification to remove non-ASCII characters +- Sort signatures that include Luhmann-style sequences +- Why are some Org links opening outside Emacs? + +** More functions for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:2e41930a-1907-47f2-956e-fef8b2459461 +:END: + +The following functions are now public, meaning that they are safe to +be used in the code of other packages or incorporated in user +configurations: + +- ~denote-identifier-p~. + +- ~denote-get-identifier-at-point~. I am implementing this in response + to a question by Alan Schmitt in issue 400: . + +- ~denote-org-extras-outline-prompt~. + +- ~denote-silo-extras-directory-prompt~. + +Consult their respective doc strings for the technicalities. + +Note that the Elisp convention is that private functions (intended for +use only inside the package) have a double dash (=--=) in their name. +In principle, these are undocumented and can change at any moment +without any notice. I do try to avoid such cases and even add warnings +when I make changes to them. Still, you should not use private +functions without understanding the risks involved. + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:be41d902-8d65-4012-aad2-d66507d34f78 +:END: + +- Wrote more unit tests for various functions. +- Improve the doc strings of several symbols (everything in the Denote + code base is documented). +- Fix some typos thanks to Nicolas Semrau and bryanrinders: + - . + - . +- Commented on all sorts of issues on the GitHub repository and many + more in private. + +** New release cycle starts in mid-September +:PROPERTIES: +:CUSTOM_ID: h:2b55dd84-6ebe-438d-aba8-97dd329ec34e +:END: + +I have many ideas for how to further refine Denote. Maybe you do too. +Though we must all wait a couple of weeks in case someone reports a +bug. This way, it is easy to fix it and publish a new minor version. +Otherwise, we may have to bundle the fix with some in-development +feature that we have not fully tested yet. + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:c0da6de0-c683-4029-9d95-06c27102dc4a +:END: + +This is just an overview of the Git commits, though remember that +there is more that goes into a project, such as the reporting of +inconsistencies, discussion of new ideas, etc.. Thanks to everybody +involved! + +#+begin_src +~/Git/Projects/denote $ git shortlog 3.0.0..3.1.0 --summary --numbered + 104 Protesilaos Stavrou + 7 Jean-Philippe Gagné Guay + 3 Ashton Wiersdorf + 1 Ad + 1 Jakub Szczerbowski + 1 bryanrinders +#+end_src + +* Version 3.0.0 on 2024-06-30 +:PROPERTIES: +:CUSTOM_ID: h:bf5e869d-548f-4c77-bf1c-b7dcf6d1d4da +:END: + +This major release comes about two years after the first version of +Denote, which was published on 2022-06-27. A lot of technicalities +have changed in the meantime, though the core idea remains the same. +In fact, the original video presentation I did is still relevant, +especially for those looking to get started with Denote (but remember +to consult the latest documentation for up-to-date information---and +ask me if you have any questions). + +Version 3 iterates on refinements that we made over the life cycle of +version 2. Existing users will find that their workflow remains the +same, though they now have even more options at their disposal. + +As usual, my release notes are detailed. Please take your time to read +them: they are here for you. + +Special thanks to Jean-Philippe Gagné Guay, a long-time contributor to +the project, for working on some of the items covered herein. I am not +covering everything, as many important changes are not user-facing. +Please consult the Git log for further details. + +** File name components can be written in any order +:PROPERTIES: +:CUSTOM_ID: h:fa0ffaf5-f762-4667-abe2-350f4ea4aac5 +:END: + +[ Relevant blog post: .] + +Users can now change the variable ~denote-file-name-components-order~ +to affect how Denote file names are constructed. By default, file +names are written using this scheme (consult the manual for the +details): + +: IDENTIFIER--TITLE__KEYWORDS.EX + +An optional =SIGNATURE= field can be added, thus: + +: IDENTIFIER==SIGNATURE--TITLE__KEYWORDS.EXT + +By modifying the ~denote-file-name-components-order~, users can +produce file names like these: + +: --TITLE__KEYWORDS@@IDENTIFIER.EXT +: __SIGNATURE--TITLE__KEYWORDS@@IDENTIFIER.EXT +: __SIGNATURE--TITLE@@IDENTIFIER__KEYWORDS.EXT + +Note that when the =DATE= is not the first component, it gets the =@@= +prefix to (i) remain unambiguous and (ii) make it easy to target it +directly for search purposes. + +Thanks to Jean-Philippe Gagné Guay for the contribution in pull +request 360: . + +We discussed the possible delimiters for the =IDENTIFIER= in issue +332: . Thanks to +Jean-Philippe, Nick Bell, Maikol Solis, and mentalisttraceur for their +insights. Our concern was to use characters that are stylistically +fine, while they are not special symbol in regular expressions (as +those make searching a bit less convenient). + +Please remember that the file-naming scheme is the cornerstone of +Denote. If you do change how your notes are named, make sure to be +consistent throughout, otherwise you will likely make it harder for +yourself to find what you need. + +** Exclude certain files from all prompts +:PROPERTIES: +:CUSTOM_ID: h:1c751e58-2f57-4aa8-9990-4ecb73054262 +:END: + +Sometimes users keep files in their ~denote-directory~ that they do +not want to interactive with. These can, for example, be what Org +produces when exporting to another file format or when archiving a +heading. + +The user option ~denote-excluded-files-regexp~ makes is possible to +omit all those files from the relevant Denote prompts. + +This is in response to requests for such a user option done by Samuel +W. Flint and zadca123 in issues 376 and 384, respectively: + +- +- + +[ Please let me know if you need this feature but do not know how to + write a regular expression. I can include concrete examples in the + manual, though I need to know about them first. ] + +** Links in plain text and Markdown files are buttonised differently +:PROPERTIES: +:CUSTOM_ID: h:9f9b978f-3c2b-4b27-9ff5-ec0ac24a76d0 +:END: + +Before we were using the function ~denote-link-buttonize-buffer~, +which would create "buttons" for all the =denote:= links it would. +Users probably had something like this in their configuration: + +#+begin_src emacs-lisp +;; DEPRECATED method +(add-hook 'text-mode-hook #'denote-link-buttonize-buffer) +#+end_src + +We now provide an approach that is technically better by using Emacs' +fontification mechanism. All the user needs is to add this to their +configuration: + +#+begin_src emacs-lisp +(add-hook 'text-mode-hook #'denote-fontify-links-mode-maybe) +#+end_src + +The notion of "maybe" in the symbol of that function is because this +will take care to be activated only in the right context. + +Thanks to Abdul-Lateef Haji-Ali for the contribution in pull request +344 (further changes by me): . + +Abdul-Lateef has assigned copyright to the Free Software Foundation. + +** How to make Org export work in a Denote silo +:PROPERTIES: +:CUSTOM_ID: h:dd39bb26-a2c0-47ad-8d54-61f1ba82d3d5 +:END: + +[ Relevant blog post: . ] + +This is not a change in Denote per se, though I have added the +relevant details in the manual. Basically, the Org export machinery +dismisses directory-local variables, thus breaking how Denote silos +work. We can work around this by having an extra =#+bind= directive in +the front matter of each file. The manual, or the aforementioned blog +post, describe the technicalities. + +** Org headings can have their own backlinks +:PROPERTIES: +:CUSTOM_ID: h:2464625e-e041-467c-a4fd-5744e3bb79c3 +:END: + +[ Relevant blog: . ] + +Denote could already link to an Org heading directly. Now it can also +generate a backlinks buffer for the current heading, using the +command ~denote-org-extras-backlinks-for-heading~. + +This is part of the optional extension =denote-org-extras.el= (it is +part of the Denote package, but not loaded by default if you use +something like =(require 'denote)=). + +I am providing this as an option for those who absolutely need it, +though in my opinion it is better to have atomic notes, such that each +file contains information that is relevant as a whole. In this +workflow, individual headings can be added or removed, but the big +picture idea of the file remain intact. + +At any rate, this change is possible due to the requisite refactoring +of the code that handles the backlinks. We can technically produce +backlinks to any pattern in files, though this may be more of interest +to developers rather than foreshadow future features in core Denote. + +** Finer control over confirmations while renaming +:PROPERTIES: +:CUSTOM_ID: h:b3dc71a6-fa0b-48d1-b8a7-2842bf725092 +:END: + +The ~denote-rename-no-confirm~ is deprecated and superseded by the +more flexible user option ~denote-rename-confirmations~. + +The command ~denote-rename-file~ (and others like it) prompts for +confirmation before changing the name of a file and updating its front +matter. The user option ~denote-rename-confirmations~ controls what +the user is prompted for, if anything. Please consult its +documentation for the technicalities. + +Thanks to Jean-Philippe Gagné Guay for the contribution in pull +request 324: . + +** The user option ~denote-save-buffer-after-creation~ is renamed to ~denote-save-buffers~ +:PROPERTIES: +:CUSTOM_ID: h:10883c47-0f5e-4267-a122-86906bf25a61 +:END: + +Please update your configuration accordingly, if you were using the +old name. + +** The commands ~denote-keywords-add~ and ~denote-keywords-remove~ are replaced by ~denote-rename-file-keywords~ +:PROPERTIES: +:CUSTOM_ID: h:042699ec-5516-44ca-93ee-60b32e599029 +:END: + +The new command can add or remove keywords. It does this by +prepopulating the minibuffer prompt with the existing keywords. Users +can then use the ~crm-separator~ (normally a comma), to write new +keywords or edit what is in the prompt to rewrite them accordingly. An +empty input means to remove all keywords. + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + ~vertico~ use the first available completion candidate instead. For + ~vertico~, the user must either move one up to select the prompt and + then type =RET= there with empty contents, or use the command + ~vertico-exit-input~ with empty contents. That Vertico command is + bound to =M-RET= as of this writing on 2024-06-30 10:37 +0300. ] + +Technically, ~denote-rename-file-keywords~ is a wrapper for +~denote-rename-file~, doing all the things that does. + +** The commands ~denote-rename-file-title~ and ~denote-rename-file-signature~ +:PROPERTIES: +:CUSTOM_ID: h:2c2c0f6f-413b-4492-b6a2-062034692c4c +:END: + +These are like the ~denote-rename-file-keywords~ we just covered. +There are wrappers of the ~denote-rename-file~ command, which are used +to change on the file name component they reference. + +If that component exists, its text is included in the minibuffer. The +user can then modify it accordingly. If there is no text, the user is +adding a new one. An empty input means to remove the title/signature +from the file altogether (again, check your minibuffer for how to +provide an empty input). + +** More commands to add/remove keywords in bulk from Dired +:PROPERTIES: +:CUSTOM_ID: h:01770801-c511-46c0-9a05-890276374b63 +:END: + +Two new specialised commands are available to help users add or remove +keywords from many files at once. These are: + +- ~denote-dired-rename-marked-files-add-keywords~ +- ~denote-dired-rename-marked-files-remove-keywords~. + +They complement the ~denote-dired-rename-marked-files-with-keywords~, +which we have had for a long time already, and which rewrites all the +keywords (instead of only adding/removing from the list). + +All three of those commands operate only on the =KEYWORDS= component +of the file name, leaving everything else as-is (while respecting the +aforementioned ~denote-file-name-components-order~). + +Thanks to Vedang Manerikar for the contribution in pull request 316: +. Vedang has already +assigned copyright to the Free Software Foundation. + +** The ~denote-org-extras-convert-links-to-file-type~ can return relative paths +:PROPERTIES: +:CUSTOM_ID: h:5f97d3f5-294d-467d-858b-281e46060625 +:END: + +The previous implementation would always return an absolute file path, +ignoring the Org user option ~org-link-file-path-type~. Whereas now it +will return a relative path if that user option is set to a value of +either ='adaptive= or ='relative=. + +Thanks to Alexandre Rousseau for the contribution in pull request 325: +. The change is small, +meaning that Alexandre does not need to assign copyright to the Free +Software Foundation. + +** For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:6911a5d6-6de3-4e04-b9d2-12a6526b4e97 +:END: + +*** The ~denote-add-prompts~ is made public +:PROPERTIES: +:CUSTOM_ID: h:28bfa74b-5a38-4f3c-a5fe-bf5a697385db +:END: + +This is used to ~let~ bind any additional prompts that should be used +by the ~denote~ command. Check the source code for how we are using +this function. + +*** The ~denote-select-linked-file-prompt~ is now public +:PROPERTIES: +:CUSTOM_ID: h:669f8eee-bdab-406a-aada-73ee8de9caf2 +:END: + +This is used internally but the commands ~denote-find-link~, +~denote-find-backlink~. Refer to the implementation of those commands +to get an idea of how to use this prompt. + +*** The ~denote-retrieve-title-or-filename~ is just a wrapper +:PROPERTIES: +:CUSTOM_ID: h:414cfd70-1850-44bf-b0fe-fb3809af302e +:END: + +It simply calls the ~denote-retrieve-front-matter-title-value~ or +~denote-retrieve-filename-title~. We do not want it to return the +~file-name-base~, as it used to, because this will duplicate the text +of the file name when there is no =TITLE= component, as demonstrated by +duli in issue 347: . + +*** The ~denote-file-prompt~ is more robust +:PROPERTIES: +:CUSTOM_ID: h:f7fb9e1f-e7b9-4dd1-a517-79069f28dcfe +:END: + +We have made this function show relative file paths for the +convenience of the user, but we take care to internally return and +store the full file path (which is unambiguous). Thanks to Alan +Schmitt for noting that the history was not working properly. This was +done in issue 339: . +A series of commits dealt with the implementation details, including a +contribution by Jean-Philippe Gagné Guay in pull request 342: +. Also read 353 for a +further set of tweaks from my side: . + +As part of these changes, the ~denote-file-prompt~ now takes a +=NO-REQUIRE-MATCH= argument. It also respects the aforementioned user +option of ~denote-excluded-files-regexp~. + +*** Relevant functions conform with the ~denote-rename-confirmations~ +:PROPERTIES: +:CUSTOM_ID: h:b056abdf-b9d9-4799-8e24-ae6507b4780e +:END: + +These include the ~denote-rename-file-prompt~ and +~denote-rewrite-front-matter~, as well as the new +~denote-add-front-matter-prompt~. + +This has the meaning of what I mentioned above. Commands that need to +deviate from the user option ~denote-rename-confirmations~ can ~let~ +bind it accordingly: we even do this for some commands in =denote.el=, +because certain prompts do not make sense there. + +*** All file name components can be ~let~ bound +:PROPERTIES: +:CUSTOM_ID: h:0118ad59-5fc2-4a19-8946-324410adb107 +:END: + +We define a new series of variables which can be set to a lexically +scoped value to control what the ~denote~ function parses. These are: + +- ~denote-use-date~ +- ~denote-use-directory~ +- ~denote-use-file-type~ +- ~denote-use-keywords~ +- ~denote-use-signature~ +- ~denote-use-template~ +- ~denote-use-title~ + +Employ those for custom extensions you may have. + +Thanks to Jean-Philippe Gagné Guay for adding those in pull request +365: . + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:1fc6d700-e8aa-4d05-bbae-5623a9565922 +:END: + +- All the Org dynamic blocks defined by Denote in the optional + =denote-org-extras.el= are now autoloaded. This means that + evaluating such a code block will work even if the user has not + explicitly used something like =(require 'denote-org-extras)=. + Thanks to Julian Hoch for asking for a relevant clarification in + issue 337: . + Thanks to Kolmas for reporting some missing autoloads in issue 371: + . + +- The value of the user option ~denote-link-backlinks-display-buffer-action~ + is slightly modified to (i) make the buffer dedicated to its window + and (ii) try to preserve its size during automatic recombinations of + the frame's layout. + +- There was a regression in version =2.3.0= relative to =2.2.0= where + the ~denote-link~ command would fail in Org capture buffers. Thanks + to Sven Seebeck for reporting this bug in issue 298: + . + +- The ~denote-filetype-heuristics~ function no longer chokes if it + gets a nil value (such as in Org capture buffers). + +- The ~denote-journal-extras-directory~ (part of the optional + =denote-journal-extras= file) falls back to ~denote-directory~ if + its value is nil. This is what the user option + ~denote-journal-extras-directory~ promises in its doc string. + +- All prompts should have their scope of application in all capital + letters, such as =Select TEMPLATE key=. The idea is to make it + easier for the user to quickly spot for the prompt is about. + +- The user option ~denote-link-description-function~ is documented in + the manual. Thanks to Sven Seebeck for noticing that we did not + document this for the =2.3.0= release. Thanks to Jean-Philippe Gagné + Guay for helping me refine the code. This was all done in issue 298: + . + +- As part of internal changes to how our various "rename" commands + work, Kolmas reported a regression with wrongly assigned file + extensions. This was done in issue 343: + . + +- In the =denote-org-extras.el= we now always jump to the correct Org + heading line, instead of missing it by 1 under certain conditions. + Thanks to kilesduli for bringing this matter to my attention in + issue 354: . + +** Policy for the aftermath of this release +:PROPERTIES: +:CUSTOM_ID: h:250e8abe-8e8d-41b9-a9dc-b2951a07d5bd +:END: + +The next few days or weeks are reserved for bug fixes. We first want +to make sure that the current code base is rock solid, before making +any further changes. Any bugs will be addressed outright and new point +releases will be published (though those are not accompanied by a +change log entry). + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:e9ea8288-99d1-407d-919a-b6024d35a501 +:END: + +Just an overview of what we did. Thanks again to everyone involved. + +#+begin_src sh +~/Git/Projects/denote $ git shortlog 2.3.0..3.0.0 --summary --numbered + 169 Protesilaos Stavrou + 52 Jean-Philippe Gagné Guay + 3 Al Haji-Ali + 2 Alan Schmitt + 1 Alexandre Rousseau + 1 Jianwei Hou + 1 Vedang Manerikar +#+end_src + +* Version 2.3.0 on 2024-03-24 +:PROPERTIES: +:CUSTOM_ID: h:e9d3ebdb-8a69-47a9-a5a2-619abc44b7d2 +:END: + +This release brings a host of user-facing refinements to an already +stable base, as well as some impressive new features. There is a lot +to cover, so take your time reading these notes. + +Special thanks to Jean-Philippe Gagné Guay for the numerous +refinements to parts of the code base. Some of these are not directly +visible to users, but are critical regardless. In the interest of +brevity, I will not be covering the most technical parts here. I +mention Jean-Philippe's contributions at the outset for this reason. +Though the Git commit log is there for interested parties to study +things further. + +** Check out the ~denote-explore~ package by Peter Prevos +:PROPERTIES: +:CUSTOM_ID: h:3e49dd9d-59db-40e5-9116-ce678231b08d +:END: + +This package provides several neat extensions that help you make +better sense of your knowledge base, while keeping it in good order. +The ~denote-explore~ package has commands to summarise the usage of +keywords, visualise connections between notes, spot infrequently used +keywords, and jump to previous historical entries. + +- Git repository: . +- Documentation: . + +Now on to Denote version =2.3.0=! + +** Link to a heading inside a Denote Org file +:PROPERTIES: +:CUSTOM_ID: h:ca7baf4f-04af-4467-a1e6-20403357280f +:END: + +Denote creates links to files by using their unique identifier. As Org +provides the =CUSTOM_ID= property for per-heading identifiers, we now +leverage this infrastructure to compose links that point to a file and +then to a heading therein. This only works for Org, as no other plain +text major mode has a concept of heading identifiers (and it is not +Denote's job to create such a feature). + +I demonstrated the functionality in a video: + + +Technically, the =denote:= link type has the same implementation +details as Org's standard =file:= and has always had this potential to +jump to a section inside the given file. + +*** The ~denote-org-store-link-to-heading~ user option +:PROPERTIES: +:CUSTOM_ID: h:a7864660-5b4c-4467-a252-9140baedeb1a +:END: + +The user option ~denote-org-store-link-to-heading~ determines whether +~org-store-link~ links to the current Org heading (such links are +merely "stored" and need to be inserted afterwards with the command +~org-insert-link~). Note that the ~org-capture~ command uses the +~org-link~ internally if it has to store a link. + +When its value is non-nil, ~org-store-link~ stores a link to the +current Org heading inside the Denote Org file. If the heading does +not have a =CUSTOM_ID=, it creates it and includes it in the heading's +=PROPERTIES= drawer. If a =CUSTOM_ID= exists, ~org-store-link~ use it +as-is. + +This makes the resulting link a combination of the =denote:= link type, +pointing to the identifier of the current file, plus the value of the +heading's =CUSTOM_ID=, such as: + +- =[[denote:20240118T060608][Some test]]= +- =[[denote:20240118T060608::#h:eed0fb8e-4cc7-478f-acb6-f0aa1a8bffcd][Some test::Heading text]]= + +Both lead to the same Denote file, but the latter jumps to the heading +with the given =CUSTOM_ID=. Notice that the link to the heading also +has a different description, which includes the heading text. + +The value of the =CUSTOM_ID= is determined by the Org user option +~org-id-method~. The sample shown above uses the default UUID +infrastructure. + +If ~denote-org-store-link-to-heading~ is set to a nil value, the +command ~org-store-link~ only stores links to the Denote file (using +its identifier), but not to the given heading. This is what Denote was +doing in all versions prior to =2.3.0=. + +Thanks to Kristoffer Balintona for discussing with me how +~org-capture~ interfaces with ~org-store-link~. I updated the +documentation accordingly. This was done in issue 267: +. + +*** Insert link to an Org file with a further pointer to a heading +:PROPERTIES: +:CUSTOM_ID: h:dd054536-8d20-4251-b23d-77fec7d7d036 +:END: + +As part of the optional =denote-org-extras.el= extension that comes +with the ~denote~ package, the command ~denote-org-extras-link-to-heading~ +prompts for a link to an Org file and then asks for a heading therein, +using minibuffer completion. Once the user provides input at the two +prompts, the command inserts a link at point which has the following +pattern: =[[denote:IDENTIFIER::#ORG-HEADING-CUSTOM-ID]][Description::Heading text]]=. + +Because only Org files can have links to individual headings, the +command ~denote-org-extras-link-to-heading~ prompts only for Org files +(i.e. files which include the =.org= extension). Remember that Denote +works with many file types. + +This feature is similar to the concept of the aforementioned user +option ~denote-org-store-link-to-heading~. It is, however, interactive +and differs in the directionality of the action. With that user +option, the command ~org-store-link~ will generate a =CUSTOM_ID= for +the current heading (or capture the value of one as-is), giving the +user the option to then call ~org-insert-link~ wherever they see fit. +By contrast, the command ~denote-org-extras-link-to-heading~ prompts +for a file, then a heading, and inserts the link at point. + +** Refinements galore to minibuffer prompts +:PROPERTIES: +:CUSTOM_ID: h:e509402b-a58f-4a10-b364-b158b31d1ee5 +:END: + +*** All commands that affect file names conform with ~denote-prompts~ +:PROPERTIES: +:CUSTOM_ID: h:11f0fc1e-552b-4a02-bf01-9d8508ce68c8 +:END: + +The scope of the ~denote-prompts~ user option is broadened to make it +more useful. In the past, this variable would only affect the +behaviour of the ~denote~ command. For example, the user would make +the command prompt for a subdirectory, then keywords, then a title. +But all other commands were not following this setting, as they were +hardcoding the prompts for title and keywords. + +Take the ~denote-subdirectory~ command as an example. It would first +prompt for a subdirectory to place the new note in, then for a title, +and then for keywords. Whereas now, it prepends the =subdirectory= +prompt to the list of ~denote-prompts~. So if the user has configured +their ~denote-prompts~ to, for example, ask for a signature and a file +type, the ~denote-subdirectory~ will do just that with the addition of +the =subdirectory= prompt. + +Same idea for all commands that either create or modify file names, +wherever conformity with ~denote-prompts~ makes sense. For example, +the ~denote-rename-file~ will never ask for a =subdirectory= because +our renaming policy is to always rename in place (to avoid +mistakes---you can always move the file afterwards). + +This also means that the ~denote-rename-file~ and its multi-file +counterpart, ~denote-dired-rename-files~, will only prompt for a +signature if it is part of the ~denote-prompts~. Whereas in the +previous version this was unconditional, thus burdening users who do +not need the =SIGNATURE= file name component (more about renaming +further into the release notes). + +Lots of Git commits went into this redesign, per my initiave in issue +247: . Thanks to +Vedang Manerikar for the changes to the convenience wrappers of the +~denote~ command (like ~denote-subdirectory~), which were done in pull +request 248: . + +Vedang has assigned copyright to the Free Software Foundation. + +Also thanks to Max Brieiev for joining the technical discussion +therein. + +The renaming commands are more intuitive now, which addresses a +discussion point raised by user babusri in issue 204: +. + +*** A simple tweak for more informative minibuffer prompts +:PROPERTIES: +:CUSTOM_ID: h:a502217d-8eff-4a6f-b66a-33e5e7ecda9d +:END: + +The text of each prompt now has all capital letters for the word +referencing its scope of its application, like =TITLE=, =KEYWORDS=, +=SIGNATURE=. The idea is to make it easier to quickly scan the text, +especially while working through multiple prompts. For example, the +prompt for a title now reads: + +: New file TITLE: + +This paradigm is followed by all prompts. It is a small yet effective +tweak to get a better sense of context. + +*** The file prompt uses relative names once again +:PROPERTIES: +:CUSTOM_ID: h:8f182ad3-c97f-45dc-a451-c552f2a7957c +:END: + +In previous versions of Denote, the minibuffer prompt to pick a file +(such as a file to link to) would show relative file names: the name +without the full file system path. The functionality depended on the +built-in =project.el= library, which did not allow us to do everything +we wanted with our prompts, such as to have a dedicated minibuffer +history or to easily enable the workflow of commands like +~denote-open-or-create~. + +In the previous version, I made the decision to remove the +=project.el= dependency and the concomitant presentation of relative +names in order to add the functionality we want. I did it with the +intention to find a better solution down the line. Et voilá! Relative +file names are back. We now have all the functionality we need. Sorry +if in the meantime you had to deal with those longer names! It was a +necessary intermediate arrangement for the greater good. + +For the technicalities, refer to the source code of the function +~denote-title-prompt~. + +*** Completion using previous inputs is now optional +:PROPERTIES: +:CUSTOM_ID: h:bcf382e4-bd00-49f3-859a-3f86e9770b77 +:END: + +All our minibuffer prompts have their dedicated history (you can +persist histories with the built-in ~savehist-mode~). They store +previous values, giving the user easy access to their past input +values. Some of our commands not only record a history, but also +leverage it to provide completion. These commands are named in the +variable ~denote-prompts-with-history-as-completion~. As of this +writing, they are: + +- ~denote-title-prompt~ +- ~denote-signature-prompt~ +- ~denote-files-matching-regexp-prompt~ + +Users who do not want to use completion for those can set the new user +option ~denote-history-completion-in-prompts~ to a nil value. + +** Renaming files got better all-round +:PROPERTIES: +:CUSTOM_ID: h:747e126a-b966-4ac8-a8ec-cf900012e37e +:END: + +One of the pillars of the ~denote~ package is its ability to rename +any file to use the efficient Denote file-naming scheme (makes file +names predictable and easy to retrieve even with rudimentary tools). +To this end, we provide several commands that affect file names, +beside the commands that create new files. + +As noted above, the commands which rename files to follow the Denote +file-naming scheme now conform with the user option ~denote-prompts~, +but there is more! + +*** A broadened scope for the ~denote-rename-no-confirm~ option +:PROPERTIES: +:CUSTOM_ID: h:f93b8075-de2d-416e-9275-7225d03678ad +:END: + +The implementation of this user option is redone (i) to save the +underlying buffer outright if the user does not want to provide their +confirmation for a rename each time and (ii) to cover all relevant +commands that perform a rename operation. The assumption is that the +user who opts in to this feature is familiar with the Denote renaming +modalities and knows they are reliable. + +The default is still the same: Denote always asks for confirmation +before renaming a file, showing the difference between the old and new +names, as well as any changes to the file's contents. In this light, +buffers are not saved to give the user the chance to further inspect +the changes (such as by running ~diff-buffer-with-file~). + +Commands that will now skip all confirmation prompts to rename the file +and, where relevant, save the corresponding buffer outright: + +- ~denote-rename-file~ +- ~denote-dired-rename-files~ +- ~denote-dired-rename-marked-files-with-keywords~ +- ~denote-rename-file-using-front-matter~ +- ~denote-rename-add-keywords~ +- ~denote-rename-remove-keywords~ +- ~denote-rename-add-signature~ (new, more below) +- ~denote-rename-remove-signature~ (new, more below) + +*** Rename a file by adding or removing a =SIGNATURE= component +:PROPERTIES: +:CUSTOM_ID: h:01ab0277-b4d4-433e-bd25-b9a0357412f6 +:END: + +The =SIGNATURE= is an optional free-form field that is part of a +Denote file name. A common use-case is to write sequence notes with +it, though Denote does not enforce any particular convention (you may +prefer to have it as a special kind of keyword for certain files that +simply stands out more due to its placement). + +[ Besides, the ~denote-sort-dired~ command lets you filter and sort + files while putting them in a fully fledged Dired buffer, so + manually sequencing notes via their signature may not be needed. ] + +We now provide two commands to add or remove a signature from file +names: + +- The ~denote-rename-add-signature~ prompts for a file and a + signature. The default value for the file prompt is the file of the + currently open buffer or the file-at-point in a Dired buffer. The + signature is an ordinary string, defaulting to the selected file's + signature, if any. + +- The ~denote-rename-remove-signature~ uses the same file prompt as + above. It performs its action only if the selected file has a + signature. Otherwise, it does nothing. + +Files that do not have a Denote file name are renamed accordingly. +Though for such cases it is better to use ~denote-rename-file~ or +~denote-dired-rename-files~ as they are more general. + +*** Use the ~denote-after-rename-file-hook~ for optional post-rename operations +:PROPERTIES: +:CUSTOM_ID: h:57f4f60c-7873-4542-a7a5-5c997cdbd137 +:END: + +All renaming commands run the ~denote-after-rename-file-hook~ after a +successful operation. This is meant for users who want to do something +specific after the renaming is done. + +** More optional features of the =denote-org-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:a0a2753e-5be9-4776-9f3f-e3b7556c13c1 +:END: + +I already covered the ~denote-org-extras-link-to-heading~, though the +file =denote-org-extras.el= has some more optional goodies for those +who work with Org files. + +*** Create a note from the current Org subtree +:PROPERTIES: +:CUSTOM_ID: h:fbf1e574-e9aa-4c67-8034-27341d7a5536 +:END: + +In Org parlance, an entry with all its subheadings and other contents +is a "subtree". Denote can operate on the subtree to extract it from +the current file and create a new file out of it. One such workflow is +to collect thoughts in a single document and produce longer standalone +notes out of them upon review. + +The command ~denote-org-extras-extract-org-subtree~ (part of the +optional =denote-org-extras.el= extension) is used for this purpose. +It creates a new Denote note using the current Org subtree. In doing +so, it removes the subtree from its current file and moves its +contents into a new file. + +The text of the subtree's heading becomes the =#+title= of the new +note. Everything else is inserted as-is. + +Read the documentation string of ~denote-org-extras-extract-org-subtree~ +or consult the manual for further details. + +*** Convert =denote:= links to =file:= links +:PROPERTIES: +:CUSTOM_ID: h:042e26e8-e3e0-4c57-9855-6b363671ae9a +:END: + +Sometimes the user needs to translate all =denote:= link types to +their =file:= equivalent. This may be because some other tool does not +recognise =denote:= links (or other custom links types---which are a +standard feature of Org, by the way). The user thus needs to (i) +either make a copy of their Denote note or edit the existing one, and +(ii) convert all links to the generic =file:= link type that +external/other programs understand. + +The optional extension =denote-org-extras.el= contains two commands +that are relevant for this use-case: + ++ Convert =denote:= links to =file:= links :: The command + ~denote-org-extras-convert-links-to-file-type~ goes through the + buffer to find all =denote:= links. It gets the identifier of the + link and resolves it to the actual file system path. It then + replaces the match so that the link is written with the =file:= type + and then the file system path. The optional search terms and/or link + description are preserved. + ++ Convert =file:= links to =denote:= links :: The command + ~denote-org-extras-convert-links-to-denote-type~ behaves like the + one above. The difference is that it finds the file system path and + converts it into its identifier. + +*** The Denote Org dynamic blocks are now in =denote-org-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:51d72c47-d434-4954-98d6-2db7a7ea6812 +:END: + +As part of this version, all our dynamic blocks are defined in the +file =denote-org-extras.el=. The file which once contained these block +definitions, =denote-org-dblock.el=, now only has aliases for the new +function names and dipslays a warning about its deprecation. + +There is no need to ~require~ the ~denote-org-extras~ feature because +all of Denote's Org dynamic blocks are autoloaded (meaning that they +work as soon as they are used). For backward compatibility, all +dynamic blocks retain their original names as an alias for the newer +one. + +We will not remove =denote-org-dblock.el= anytime soon to avoid any +potential breakage with people's existing notes. Though if you are new +to this functionality, you better avoid the deprecated symbols. + +*** Org dynamic block to only insert missing links +:PROPERTIES: +:CUSTOM_ID: h:45176e63-c609-40f6-a11d-1cc0c28460dd +:END: + +The =denote-missing-links= block is available with the command +~denote-org-extras-dblock-insert-missing-links~. It is like the +=denote-links= block (documented at length in the manual), except it +only lists links to files that are not present in the current buffer. +The parameters are otherwise the same: + +: #+BEGIN: denote-missing-links :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :id-only nil +: +: #+END: + +Remember to type =C-c C-x C-u= (~org-dblock-update~) with point on the +=#+BEGIN= line to update the block. + +This brings back a feature that was deprecated in version 2.2.0, but +makes changes to it so that (i) it is more limited in scope and (ii) +available as a standalone Org dynamic block. + +Thanks to Stephen R. Kifer, Peter Prevos, and Elias Storms for the +discussion which made it clear to me that users do have a need for +such functionality. This was done in the now-defunct mailing list: +. + +Also thanks to Vedang Manerikar for fixing an edge case bug. This was +done in pull request 260: . + +Org dynamic blocks are a powerful feature which also showcases how far +we can go with Denote's efficient file-naming scheme. + +** Quality-of-life improvements +:PROPERTIES: +:CUSTOM_ID: h:08f27f36-0ed2-4a5e-b02b-f0075c6e904f +:END: + +Here I include other changes we made to existing functionality. + +*** BREAKING User-defined sluggification of file name components +:PROPERTIES: +:CUSTOM_ID: h:240b80e7-242c-46fb-83d2-1ba36bdcaf66 +:END: + +In the previous version, we introduced the user option +~denote-file-name-letter-casing~. This was used to control the letter +casing of file name components, but was ultimately not flexible enough +for our purposes. We are thus retiring it and replacing it with the +more powerful, but also more advanced, user option +~denote-file-name-slug-functions~. + +For existing users of the deprecated functionality, you can still +preserve the input of a prompt verbatim with something like this: + +#+begin_src emacs-lisp +(setq denote-file-name-slug-functions + '((title . denote-sluggify-title) + (keyword . identity) + (signature . denote-sluggify-signature))) +#+end_src + +The manual explains the details and shows ready-to-use code samples. + +Remember that deviating from the default file-naming scheme of Denote +will make things harder to use in the future, as files will have +permutations that create uncertainty. The sluggification scheme and +concomitant restrictions we impose by default are there for a very +good reason: they are the distillation of years of experience. Here we +give you what you wish, but bear in mind it may not be what you need. +You have been warned. + +Thanks to Jean-Philippe Gagné Guay for introducing this variable, +among other tweaks, in pull request 217: . +Jean-Philippe has assigned copyright to the Free Software Foundation. + +*** Option to automatically save the buffer of a new note +:PROPERTIES: +:CUSTOM_ID: h:3e1249f1-ac26-4187-9ddd-7391b4e5131f +:END: + +The user option ~denote-save-buffer-after-creation~ controls whether +commands that create new notes save their buffer right away. + +The default behaviour of commands such as ~denote~ (or related) is to +not save the buffer they create. This gives the user the chance to +review the text before writing it to a file. The user may choose to +delete the unsaved buffer, thus not creating a new file on disk. + +If ~denote-save-buffer-after-creation~ is set to a non-nil value, such +buffers are saved automatically and so the file is written to disk. + +*** The ~denote-menu-bar-mode~ and the placement of the Denote submenu +:PROPERTIES: +:CUSTOM_ID: h:c8336927-cf6b-4770-b041-123bf9186e57 +:END: + +The command ~denote-menu-bar-mode~ toggles the inclusion of the +submenu with the Denote entries in the Emacs menu bar (which is on +display when ~menu-bar-mode~ is enabled). + +This submenu is now shown after the =Tools= entry. + +Thanks to Joseph Turner for sending me the relevant patches. Joseph +has assigned copyright to the Free Software Foundation. + +*** The =C-c C-o= works in ~markdown-mode~ for Denote links +:PROPERTIES: +:CUSTOM_ID: h:1c884b19-7ab7-4eb5-a332-815d25f7373c +:END: + +In files whose major mode is ~markdown-mode~, the default key binding +=C-c C-o= (which calls the command ~markdown-follow-thing-at-point~) +correctly resolves =denote:= links. This method works in addition to +the =RET= key, which is made available by the buttonization that we +also provide. Interested users can refer to the function +~denote-link-markdown-follow~ for the implementation details. + +Thanks to user pmenair for noting a case where this was breaking +general Markdown linking functionality. This was done in issue 290: +. + +*** More fine-grained control of Denote faces for dates/identifiers +:PROPERTIES: +:CUSTOM_ID: h:c6f739ef-ea26-41b8-84e6-c87c4622cdba +:END: + +We now define more faces for fine-grained control of the identifier in +Dired. Thanks to mentalisttraceur for suggesting the idea in issue +276: . + +Before you ask, no, none of my themes will cover those faces because +extra colouration is something only the user can decide if they want +or not. In the above link I provide a sample with a screenshot (apart +from the ~modus-themes~, my ~ef-themes~ and ~standard-themes~ have +similar functionality): + +#+begin_src emacs-lisp +(defun my-modus-themes-denote-faces (&rest _) + (modus-themes-with-colors + (custom-set-faces + `(denote-faces-year ((,c :foreground ,cyan))) + `(denote-faces-month ((,c :foreground ,magenta-warmer))) + `(denote-faces-day ((,c :foreground ,cyan))) + `(denote-faces-time-delimiter ((,c :foreground ,fg-main))) + `(denote-faces-hour ((,c :foreground ,magenta-warmer))) + `(denote-faces-minute ((,c :foreground ,cyan))) + `(denote-faces-second ((,c :foreground ,magenta-warmer)))))) + +(add-hook 'modus-themes-post-load-hook #'my-modus-themes-denote-faces) +#+end_src + +*** New convenience command for users of the optional =denote-journal-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:9e7bff88-a6ad-45e7-b802-0493153e0e20 +:END: + +The command ~denote-journal-extras-link-or-create-entry~ links to the +journal entry for today or creates it in the background, if missing, +and then links to it from the current file. If there are multiple +journal entries for the same day, it prompts to select one among them +and then links to it. When called with an optional prefix argument +(such as =C-u= with default key bindings), the command prompts for a +date and then performs the aforementioned. With a double prefix +argument (=C-u C-u=), it also produces a link whose description +includes just the file's identifier. + +Thanks to Alan Schmitt for contributing this command, based on +previous discussions. It was done in pull request 243: +. + +** For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:03778c8c-60aa-449c-96df-7e41916668a6 +:END: + +These has new parameters or are new symbols altogether. Please read +their respective doc string for the details. + ++ Function ~denote-convert-file-name-keywords-to-crm~. ++ Function ~denote-valid-date-p~. ++ Function ~denote-parse-date~. ++ Function ~denote-retrieve-title-or-filename~. ++ Function ~denote-get-identifier~. ++ Function ~denote-signature-prompt~. ++ Function ~denote-file-prompt~. ++ Function ~denote-keywords-prompt~. ++ Function ~denote-title-prompt~. ++ Function ~denote-rewrite-front-matter~. ++ Function ~denote-rewrite-keywords~. ++ Function ~denote-update-dired-buffers~. ++ Function ~denote-format-string-for-org-front-matter~. ++ Function ~denote-format-string-for-md-front-matter~. ++ Variable ~denote-link-signature-format~. ++ Function ~denote-link-description-with-signature-and-title~. ++ Variable ~denote-link-description-function~. + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:040f2678-674d-4e99-b428-659cd3a3b7c3 +:END: + +- The ~denote-sort-dired~ function no longer errors out when there is + no match for the given search terms. Thanks to Vedang Manerikar for + the initial patch! This was done in the now-defunct mailing list: + . Further + changes by me. + +- The ~denote-keywords-sort~ function no longer tries to sort keywords + that are not a list. Thanks to Ashton Wiersdorf for the patch. The + change is small. As such, Ashton does not need to assign copyright + to the Free Software Foundation. + +- Documented in the manual that custom convenience commands can be + accessed by the ~denote-command-prompt~. Thanks to Glenna D. for + clarifying the language. + +- The ~denote-user-enforced-denote-directory~ is obsolete. Those who + used it in their custom code can simply ~let~ bind the value of the + variable ~denote-directory~. Thanks to Jean-Philippe Gagné Guay for + making the relevant changes (the Git history is not direct here and + I cannot quickly find the pull request---the commit is =a48a1da=). + +- The ~denote-link-return-links~ no longer keeps buffers around. + Thanks to Matteo Cavada for the patch. This was done in pull request + 252: . The change is + small and so Matteo does not need to assign copyright to the Free + Software Foundation. + +- Thanks to user jarofromel (recorded in Git as "random" author) for + fixing a mismatched parenthesis in ~denote-parse-date~. This was + done in pull request 258: . + +- The ~denote-rename-buffer-mode~ now works as expected with + non-editable files, like PDFs. Thanks to Alan Schmitt for bringing + this matter to my attention and then refining the implementation + details in pull request 268: . + +- All the Denote linking functions can be used from any file outside + the ~denote-directory~ (links are still resolved to files inside the + ~denote-directory~). Thanks to Jean-Philippe Gagné Guay for the + contribution in pull request 236: . + +- We removed all glue code that integrated Denote with the built-in + ~ffap~, ~xref~, and ~project~ libraries. We may reconsider how best + to organise such features in the future. Thanks to Noboru Ota + (nobiot), who originally contributed those extensions, for + suggesting their removal from our code base. We did this by + evaluating all use-cases. The discussion with Noboru happened in + issue 264: . Also + thanks to Jean-Philippe Gagné Guay and Alan Schnmitt for checking + the impact of this on how we generate backlinks. The latest + iteration of this was done in pull request 294, by Jean-Philippe: + . + +- While renaming files, signatures no longer lose consecutive spaces. + Thanks to Wesley Harvey for the contribution in pull request 207: + . The change is + within the ~15 line limit and so Wesley does not need to assign + copyright to the Free Software Foundation. + +- All of the above and lots more are documented at length in the + manual. This is a big task in its own right (as are release notes, + by the way), though it ensures we keep a high standard for the + entire package and can communicate all our knowledge to the user. + +** No more SourceHut +:PROPERTIES: +:CUSTOM_ID: h:9a0d6afc-95e0-490e-a573-5a50fe7bdf28 +:END: + +Development continues on GitHub with GitLab as a mirror. I explained +my reasons here: . + +This is a change that affects all my Emacs packages. + +** Forward guidance for Denote version 3.0.0 +:PROPERTIES: +:CUSTOM_ID: h:61fb340e-5c7c-4a4b-927c-63faf4759a09 +:END: + +We will not any new features until mid-April or a bit later if +necessary. This gives users enough time to report any potential issues +with version =2.3.0=. If there are any bugs, they will be fixed right +away and new minor releases will be introduced (though without release +notes). + +Once we are done with this release cycle, we want to prepare for the +next major version of Denote. The plan is to make the placement of +file name components entirely customisable, among many other power +user features. Though the defaults will remain intact. + +For the immediate future, please prioritise bug reports/fixes. Then +see you around for another round of hacking. The Denote code base is a +pleasure to work with due to how composable everything is. I happy to +make it even better for developers and users alike. + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:a6fd8e16-ded9-49cf-afbb-6e1373c3c43d +:END: + +Just an overview of what we did. Thanks again to everyone involved. + +#+begin_src sh +~/Git/Projects/denote $ git shortlog 2.2.0..2.3.0 --summary --numbered + 246 Protesilaos Stavrou + 46 Jean-Philippe Gagné Guay + 6 Vedang Manerikar + 3 Joseph Turner + 2 Alan Schmitt + 2 Max + 2 Peter Prevos + 1 Ashton Wiersdorf + 1 Glenna D. + 1 Matteo Cavada + 1 mattyonweb + 1 random + 1 wlharvey4 +#+end_src + +** All contributions are valuable +:PROPERTIES: +:CUSTOM_ID: h:967372fa-933b-40d2-b1a8-546d1a50d35d +:END: + +I encourage you to provide feedback on any of the functionality of the +Denote package. You do not need to be a developer or indeed an expert +in Emacs. When you have an idea in mind on how you use Denote, or you +think something could be done differently, please speak your mind. I +do listen to feedback and am interested in further improving this +package. Everybody is welcome! + +* Version 2.2.0 on 2023-12-10 +:PROPERTIES: +:CUSTOM_ID: h:8efed390-cfa0-420d-b300-0cb76bf2c9f9 +:END: + +The present version covers four broad themes: + +1. Denote rename commands are more user-friendly and featureful. +2. An optional sorting facility makes it possible to produce a + filtered and sorted Dired buffer with Denote files. +3. The optional Denote Org dynamic blocks have received a lot of attention. +4. Bug fixes and internal refinements. + +[ Remember that you do not need to be a programmer to contribute to + Denote. Report a bug, make a suggestion, or just describe how you + want to use this package. Every idea counts and we may implement it + if we can. ] + +** The rename commands can remove a Denote file name component +:PROPERTIES: +:CUSTOM_ID: h:54d803d8-4863-4160-bb2f-3302fb8bff23 +:END: + +The commands we provide to rename files using the Denote file-naming +scheme---~denote-rename-file~, ~denote-dired-rename-files~, and +~denote-dired-rename-marked-files-with-keywords~---can now remove +Denote file name components. This is done by providing an empty string +at the relevant prompt. + +For example, to remove the =TITLE= component from a file called +=20231209T110322==sig--title__keywords.ext= we provide an empty string +at the title prompt. The end result will look something like this: +=20231209T110322==sig__keywords.ext=. + +All prompts now include a hint that leaving them empty will ignore the +given field if it does not exist or remove it if it does exist. + +Note that you must *check how to input an empty string* with your +minibuffer user interface of choice. For instance, with the ~vertico~ +package you can do that with the =M-RET= key binding or by selecting +the prompt line directly (notice the counter showing something like +=*/5= instead of =1/5=). Please make sure to consult the documentation +of the package you are using as this behaviour is not controlled by +Denote. Vertico, and others like it, selects the first candidate if +you type =RET= without any input, which is not the same as an empty +string---it is the first candidate. + +Also read the Denote manual on the matter of [[https://protesilaos.com/emacs/denote#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]. In short, +we use this facility to name all our files, regardless of file type, +in a consistent way that makes them easier to find (I do this with my +videos, for example, and I do it across my filesystem for all personal +files). + +** The file-to-be-renamed is easier to read in the minibuffer +:PROPERTIES: +:CUSTOM_ID: h:69d85d3b-0200-4cc1-baff-9d59aa0ff57b +:END: + +The commands ~denote-rename-file~ and ~denote-dired-rename-files~ +show the name of the file they are operating on in the minibuffer +prompt. This is now produced relative to the current directory, +meaning that instead of =/some/rather/long/path/to/file-name.txt= +Denote only displays =file-name.txt=. + +Our rename commands never move files to another directory, anyway, so +we do not need to remind the user of the entire file system path. + +To make things easier for users/themes, file names highlighted in +Denote prompts are fontified with either of following faces, +depending on the specifics of the case: + +- ~denote-faces-prompt-old-name~ +- ~denote-faces-prompt-new-name~ +- ~denote-faces-prompt-current-name~ + +These faces inherit the attributes of basic faces, so they should look +decent without further tweaks across all themes. + +** Prompts for title, keywords, and signature accept an empty string +:PROPERTIES: +:CUSTOM_ID: h:5897bcc1-4637-4268-8518-8404d939b4b9 +:END: + +The prompts defined by Denote that apply to file name components all +accept an empty string. This has the effect of skipping the given +component. For example, we can create a file without a title and +keywords, with the following sequence of actions (I assume you are +using ~vertico~ for the minibuffer user interface): + +- Type =M-x denote=. +- Type =M-RET= at the title prompt to input an empty string. +- Now type =M-RET= at the keywords prompt for another empty string. + +The resulting file name is something like =20231209T110950.org=. + +** Dired with sorting and filtering +:PROPERTIES: +:CUSTOM_ID: h:05aa437b-2fc8-4e01-ac38-ab77baad83af +:END: + +The new optional =denote-sort.el= library provides facilities to sort +Denote files by any of their file name components. Users can benefit +from this facility to produce a filtered and sorted listing of Denote +files with the command ~denote-sort-dired~. + +~denote-sort-dired~ produces a fully fledged Dired buffer. It asks for a +regular expression that matches file names in the ~denote-directory~. +It then prompts for a sort key and finally checks with the user +whether to reverse the order or not. + +[ Do not be discouraged by the term "regular expression". Ordinary + words work fine. Plus, with Denote's file-naming scheme we have + semantics such as =_keyword=, =-title=, ~=signature~, as explained + in the manual. This is the whole point of using a thoughtful naming + scheme. ] + +The resulting Dired listing is flat, meaning that files inside of +subdirectories are bundled together with those present at the root of +the ~denote-directory~. In this case, files inside of a subdirectory +include the directory component as a prefix. So we have something like +this: + +#+begin_example +test-subdir/20230320T105950--a-new-note__testing.txt +20231202T095629--rename-works-as-intended__one_test_two.org +#+end_example + +I think this is a killer feature, as the fully fledged Dired buffer +allows us to perform all supported operations on our Denote +sorted+filtered files (e.g. change file permissions, move files to +another directory, or open them in an external application). + +I recorded a video to show how this works: +. + +[ Remember that we can rename any file using the Denote file-naming + scheme, meaning that our files can include stuff like PDFs and + videos. Combine this with the concept of "silos", which is covered + in the Denote manual, to organise your long-term storage and + retrieve it efficiently. ] + +** Combine contents of files with an Org dynamic block +:PROPERTIES: +:CUSTOM_ID: h:d41009c1-9833-4b28-8240-9666bfd26559 +:END: + +The new =denote-files= Org dynamic block produces a continuous stream +of file contents. It joins together the contents of files inside the +~denote-directory~ whose name matches the given regular expression. +Optional parameters control whether to include links to those files, +omit their front matter, sort by a given file name component, or tweak +the separator between each file's contents. + +I produced a video to demonstrate the functionality: +. + +Use the command ~denote-org-dblock-insert-files~ to insert such a +block directly at point. Read the Denote manual for the +technicalities: [[https://protesilaos.com/emacs/denote#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic block to insert file contents]]. + +[ Videos I do will eventually be out-of-date. The manual is the source + of truth. ] + +Bear in mind that this feature is not "transclusion". We are simply +printing a copy of the contents of the files in the current buffer. +Changes made to this copy are not reflected in the original files. + +The =denote-files= Org dynamic block is an excellent way to quickly +collect your thoughts on a given topic. Although dynamic blocks are a +feature of Org, the contents of the files do not need to be in Org +syntax (I write most of my notes in plain text (=.txt=)). + +Thanks to Claudiu Tănăselia for proposing this idea and discussing it +with me. This was done via a private channel and the information is +shared with permission. + +** Sort parameters are used in all Denote Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:7b51fe38-302e-488d-9816-7015a8071ddb +:END: + +All Denote Org dynamic blocks make use of =denote-sort.el= (described +further above). It powers the =:sort-by-component= and =:reverse-sort= +parameters. + +Thanks to Glenna D. for suggesting this feature and discussing it with +me. This was done via a private channel and the information is shared +with permission. It is what inspired me to start work on +=denote-sort.el=, which I then extended to cover Dired, as noted +above. + +** The =:missing-only= parameter is removed from Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:2bd26aef-70ad-4d83-a4ab-c75a893a733a +:END: + +I am removing it because the underlying functionality of +~denote-add-missing-links~ was not always reliable. + +** Files with signature are linked appropriately in Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:144436eb-e674-4052-ac0a-d582b6aa2f53 +:END: + +In general, we provide the command ~denote-link-with-signature~ to let +the user pick a file that has a signature and link to it. The +description of such a link contains the signature text as well as the +file title. The ~denote-link-with-signature~ is distinct from the +standard ~denote-link~, as it allows the user to express intent about +the inclusion of the signature. + +In Org dynamic blocks for links/backlinks, we make this happen +automatically since there can be no manual intervention to express +intent on a link-by-link basis. + +** Fontification in Dired can now extend to subdirectories +:PROPERTIES: +:CUSTOM_ID: h:46e08576-4c17-4b1e-a268-e0223250e7c1 +:END: + +The user option ~denote-dired-directories~ activates the +~denote-dired-mode~ in the specified list of directories when the user +sets this in their init file: + +#+begin_src emacs-lisp +(add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) +#+end_src + +The new user option ~denote-dired-directories-include-subdirectories~ +extends the reach of this feature to all subdirectories thereof. + +Thanks to Henrik Hörmann for discussing this with me and contributing +a patch. This was originally done in pull request 191 on the GitHub +mirror: . Subsequent +refinements by me. + +** Signatures are sluggified as intended +:PROPERTIES: +:CUSTOM_ID: h:73e1efaa-2c22-48ee-be46-072b55177c99 +:END: + +The file name signature component is now sluggified properly. This +means that multiple words are separated by the equals sign, in +accordance with the Denote file-naming scheme where a word separator +is the same as the given field separator (this is the low-tech feature +that makes Denote files so easy to retrieve without fancy extras). + +Vedang Manerikar fixed two relevant bugs in the "rename" commands, +while I rewrote internal functions and tests in the interest of consistency. Vedang's patches: . + +[ The "signature" is a free form component of the file name. Users can + add anything they want there, such as to use it as a "category" that + is different from "tags/keywords", or to introduce sequences in + their notes, or to just have an extra marker for files they need to + spot quickly. ] + +** For developers +:PROPERTIES: +:CUSTOM_ID: h:79f2fd7e-d5a7-4c78-bce7-f8d21e86e32c +:END: + +There is a section in the manual titled "For developers or advanced +users". There we document functions or variables that are +public-facing, meaning that we test and document their behaviour and +encourage others to use them for code they write on top of Denote. +Refer to this section if you are looking to extend Denote. Though you +can also just check the source code, which is designed to be readable +and hackable. + +- The ~denote-directory-files~ function gains new functionality that + subsumes that of the now-deprecated functions + ~denote-directory-files-matching-regexp~, ~denote-all-files~, + ~denote-directory-text-only-files~. Thanks to Jean-Philippe Gagné + Guay for the contribution, which was done in pull request 195 on the + GitHub mirror: . + +- The font-lock keywords we define are consolidated into a single + variable: ~denote-faces-file-name-keywords~ instead of being split + into two variables. This means that we cover all our fontification + needs in the backlinks buffer as well as the ~denote-dired-mode~ + with this one point of entry. It also works for ~denote-sort-dired~, + which can include files with their subdirectory component in the + same flat listing. + +- Use the function ~denote-retrieve-filename-keywords~ to extract + keywords from the file name alone, without going into the file + contents. + +- The ~denote-retrieve-filename-title~ function now returns an empty + string if no title is present. Its behaviour is thus consistent with + ~denote-retrieve-filename-keywords~ and ~denote-retrieve-filename-signature~. + +- The ~denote-retrieve-filename-title~ will now use the + ~file-name-base~ function as a fallback subject to a non-nil + optional argument. This case come into effect when the file does not + have a title component. The new optional argument allows the caller + to handle such cases as they see fit. + +- The ~denote-signature-prompt~ and ~denote-title-prompt~ functions + accept an optional =DEFAULT-SIGNATURE= or =DEFAULT-TITLE= argument. + Internally, this is used as the =INITIAL-INPUT= of ~completing-read~ + instead of the =DEF= argument. This matters because we want the + prompt to return an empty string if there is no input, whereas the + presence of =DEF= means that =DEF= is returned when the prompt is + empty. + +- All our functions that interactively match file names with a regular + expression now use the ~denote-files-matching-regexp-prompt~ + function. When called from Lisp, it takes a =REGEXP= argument as + well as an optional =PROMPT-TEXT=. + +For the purposes of this release cycle, I am not documenting the +points of entry provided by =denote-sort.el=. It is a new feature that +I may eventually incorporate in =denote.el=. If you are interested in +the functionality (e.g. to have more elaborate sorting algorithms), +please take a look at the source code and then let us discuss the +implementation details. + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:ce5c7865-9ec1-49ba-9388-5a251ab56735 +:END: + +- Rewrote the manual on the topic of Org dynamic blocks. Same idea for + practically the entirety of =denote-org-dblock.el=. + +- Marked the interactive specification of a few commands with the + major mode they belong to. This means that =M-X= (note the capital + X), which calls ~execute-extended-command-for-buffer~ by default, + will only show those commands in the relevant context. + +- Made internal refinements and simplified the implementation of a few + functions. This is important work to keep the code base clean and + easy to read/maintain. Thanks to Jean-Philippe Gagné Guay for the + contribution. It was done in pull request 193 on the GitHub mirror: + . + +- Improved the doc string of the ~denote-format-file-name~ function. + Also introduced a unit test for it to be sure it does what we expect + (I eventually want to have tests for everything we do, but this is a + long-term project). + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:6830d3f3-130c-4346-b3ca-a3d4b0e9f974 +:END: + +Just an overview of what we did. Thanks again to everyone involved. + +#+begin_src sh +~/Git/Projects/denote $ git shortlog 2.1.0..2.2.0 --summary --numbered + 125 Protesilaos Stavrou + 17 Jean-Philippe Gagné Guay + 2 Vedang Manerikar + 1 Henrik Hörmann +#+end_src + +** Policy for the next development cycle +:PROPERTIES: +:CUSTOM_ID: h:cb0cae4f-c9a1-40b3-98ae-781a57270d4e +:END: + +I will give a ~1 week pause on Denote development before making any +feature changes. This is to ensure that we catch possible bugs and +push fixes right away. If there are other changes in place, it is not +possible to make point updates of this sort, as we must first wait for +the new features to be tested in real-world scenaria. + +* Version 2.1.0 on 2023-11-12 +:PROPERTIES: +:CUSTOM_ID: h:167beb8f-14be-40de-a1f2-d13910924c00 +:END: + +The general theme of this release is improvements to the quality of +life with Denote. While these release notes and the overall +documentation are comprehensive, make no mistake: Denote can be used +with =M-x denote=, =M-x denote-link=, =M-x denote-backlinks=, =M-x +denote-rename-file=. These have been rock solid from the beginning. +Everything else is for more specialised workflows. + +I hope to produce a companion video to this changelog in the coming +days. Though I am still reeling from the injury to my left hand (I +wrote all this to not delay the package any longer). Please check back +in my website's coding blog section to find the follow-up video: +. + +[ Remember to consult the manual whenever you have a question about + Denote. It is comprehensive and, in my opinion, a paradigm of how + free software should be done for the benefit of users. I document + everything in detail and am eager to continue this way. If something + is unclear, contact me in person, use the mailing list, or open an + issue on the GitHub/GitLab mirror. I do not check other fora or + media and will thus not help you there. If you are writing custom + code, remember to read the doc strings. I write them for you too. ] + +** Deprecated the ~denote-allow-multi-word-keywords~ +:PROPERTIES: +:CUSTOM_ID: h:a086d1d2-adb3-4151-a7af-813d79b4b3dc +:END: + +This user option enabled the use of keywords that consisted of +multiple words. Those would be separated by hyphens. Such keywords do +not work as Org =#+filetags= and also mess up with the neat search +semantics of Denote's file-naming scheme where a hyphen prefix +anchors the query to the =TITLE= component of the name. + +Users who absolutely need multi-word keywords are encouraged to use +the new ~denote-file-name-letter-casing~ option. More below. + +** Control the letter casing of file name components +:PROPERTIES: +:CUSTOM_ID: h:29319b8a-698b-4a1c-bab4-7b106a623de8 +:END: + +By default, Denote downcases all components of the file name. The user +option ~denote-file-name-letter-casing~ provides granular control over +this behaviour. + +The value it accepts is an alist where each element is a cons cell of +the form =(COMPONENT . METHOD)=. The manual, or the variable's doc +string, cover the details. The gist is that we can now instruct Denote +to accept input verbatim, such as because we want to apply a +=camelCase= convention or variants thereof. + +Here is an example, where we downcase the title, but preserve the +letter casing of the signature and keyword components with this: + +#+begin_src emacs-lisp +(setq denote-file-name-letter-casing + '((title . downcase) + (signature . verbatim) + (keywords . verbatim) + (t . downcase))) +#+end_src + +Users of the now-deprecated ~denote-allow-multi-word-keywords~ are +encouraged to implement a letter casing convention with the help of +this new user option. + +Relevant sections in the manual: + +- The file-naming scheme: + . +- Contol the letter casing of file names: + + +** The ~denote-dired-mode~ should now work while toggling ~wdired~ +:PROPERTIES: +:CUSTOM_ID: h:18a3b515-9306-4911-ba2d-73e36efbdd32 +:END: + +The writable version of Dired would break the colouration applied by +~denote-dired-mode~. I have arranged for this to not happen anymore, +although it means that I had to add an advice to relevant wdired +symbols because no proper hook is on offer. + +** The "do or create" commands are more intuitive to use +:PROPERTIES: +:CUSTOM_ID: h:5bcdc4b8-ecba-44d7-accc-0b26657aa29b +:END: + +Denote provides several commands with a "do or create" logic. For +example, the ~denote-open-or-create~ prompts for a file to visit: if +something matches the user's input, it is visited in a buffer, +otherwise a new note is created with the given input. Same for +~denote-link-or-create~, mutatis mutandis. + +Before, the "... or create" step did not make it obvious how the +previous search terms could be reused. Whereas now those are set as +the default minibuffer value at the title prompt, meaning that typing +=RET= at the empty prompt will use that value, while =M-n= +(~next-history-element~ with default settings) will put the text into +the prompt for further editing. + +I will answer this because I get asked about it: we still refrain from +creating the new note outright because the search terms are not +necessarily suitable for a new title. Remember that Denote's file name +is optimised for searching: =-word= is specific to the title, =_word= +to the keywords, and ==word= to the signature. Combine this with the +~orderless~ package and you frequently type something like =_jou -he= +to match a file with the =journal= keyword and the word =hesitation= +in its title. + +*IMPORTANT NOTE:* some minibuffer completion User Interfaces preselect +the first completion candidate, which is not always the same as the +default value. Check with your UI of choice how to pass a default +value and/or provide an empty input. For example, with the ~vertico~ +package one can move up from the first candidate to select the prompt +itself (the counter switches from =1/N= to =*/N=). + +Relevant sections in the manual: + +- Open an existing note or create it if missing: + . +- Link to a note or create it if missing: + . + +*** New "... or create with command" features for more flexibility +:PROPERTIES: +:CUSTOM_ID: h:6f475151-9d64-4dfb-8c59-694c93d56ce8 +:END: + +As part of the wider "do or create" feature set, Denote provides the +option to run a specific note-creating command instead of just using +the standard ~denote~ one. For example, it is possible to call the +~denote-subdirectory~ command to pick a subdirectory of the +~denote-directory~ for the new note. Commands providing this facility +are ~denote-open-or-create-with-command~ and ~denote-link-after-creating-with-command~. + +Thanks to Vedang Manerikar for fixing a broken ~if~ clause during +development: . + +** The title and signature prompts use minibuffer completion +:PROPERTIES: +:CUSTOM_ID: h:429847c8-ebf4-4b23-a597-5276309ef61a +:END: + +All Denote minibuffer prompts come with their own history. This means +that =M-p= (~previous-history-element~) and =M-n= +(~next-history-element~) always return relevant input. + +The title and signature prompts now reuse their input history to +provide completion. This means that the user can quickly access +previous inputs, either to pass them directly or edit them further +before inputting them. + +[ Use the built-in ~savehist-mode~ to persist histories across sessions. ] + +Remember to check with your minibuffer UI on how to input empty +values at the prompt, should you ever need to do so. + +For posterity, I first implemented this in commit =0d855bb=. However, +it did not work with the default minibuffer because the =SPC= key +performs completion (popping up the Completions buffer). So users +could not easily input an arbitrary string for the title/signature. I +thus reverted that commit in =9f692cb=. + +[ The bug was reported by Suhail Singh on the mailing list: + . ] + +Stefan Monnier suggested the use of the ~minibuffer-with-setup-hook~, +which lets us disable =SPC= completion for the purposes of these +functions. This is most welcome as the functionality is nice to have. +Stefan's feedback was provided on the emacs-devel mailing list: +. + +** Create a note with the region's contents +:PROPERTIES: +:CUSTOM_ID: h:ae798d1f-6fa2-4d99-91c9-0d5eb18b1bb0 +:END: + +The command ~denote-region~ takes the contents of the active region +and then prompts for a title and keywords. Once a new note is +created, it inserts the contents of the region therein. This is +useful to quickly elaborate on some snippet of text or capture it for +future reference. + +It also provides the ~denote-region-after-new-note-functions~ abnormal +hook. Read the manual for more: +. + +** Comprehensive refinements to the ~denote-rename-buffer-mode~ +:PROPERTIES: +:CUSTOM_ID: h:91b3ba9f-8b10-4f1c-a08b-70f5e7140923 +:END: + +This is an opt-in feature that automatically renames the buffer of +newly visited Denote files according to the user's preferences. Not to +be confused with renaming files: buffers are internal to Emacs. Enable +it at startup by adding this to your configuration file: + +#+begin_src emacs-lisp +(denote-rename-buffer-mode 1) +#+end_src + +Relevant entries in the manual: + +- Automatically rename Denote buffers: + . +- The ~denote-rename-buffer-format~ option: + . + +*** The ~denote-rename-buffer-format~ option +:PROPERTIES: +:CUSTOM_ID: h:beeafe57-f110-4c11-87e7-10f682ca2386 +:END: + +The user option ~denote-rename-buffer-format~ controls how the +function ~denote-rename-buffer~ chooses the name of the +buffer-to-be-renamed. This function is the one used by the +~denote-rename-buffer-mode~. + +Users may want, for example, to include some text that makes Denote +buffers stand out, such as a =[D]= prefix. Examples: + +#+begin_src emacs-lisp +;; Use the title (default) +(setq denote-rename-buffer-format "%t") + +;; Use the title and keywords with some emoji in between. +(setq denote-rename-buffer-format "%t 🤨 %k") + +;; Use the title with a literal "[D]" before it +(setq denote-rename-buffer-format "[D] %t") +#+end_src + +Users who need yet more flexibility are best served by writing their +own function and assigning it to the ~denote-rename-buffer-function~ +(in such a case, please contact me as I am curious to know what the +underlying need is). + +The manual or doc string of ~denote-rename-buffer-format~ cover the +technicalities of the available format specifiers. + +Thanks to Jean-Philippe Gagné Guay for intermediately refining parts +of the code. This was done in pull request 177 on the GitHub mirror: +. + +Thanks to Vedang Manerikar for ensuring that the string of the buffer +is trimmed so that it never starts with an empty space (those buffers +count as "internal" to Emacs and are not shown to the user): +. + +*** The ~denote-rename-buffer-mode~ also works with unsaved buffers +:PROPERTIES: +:CUSTOM_ID: h:e65bb546-af22-45fb-a918-d0e621b0e415 +:END: + +Internal refinements to a Denote Lisp macro make this minor mode also +work with new and unsaved Denote buffers. Whereas before only the +buffers of existing files would be renamed. + +** Denote's renaming facilities are better than ever +:PROPERTIES: +:CUSTOM_ID: h:703b9021-f917-4b3f-9406-14992b2a4fe8 +:END: + +Denote's value proposition is its efficient file-naming scheme that +makes it easier to retrieve files even with rudimentary search tools. +We provide several commands to rename existing files according to this +scheme. The underlying file type does not matter (e.g. I use Denote to +name my video files). + +Relevant sections in the manual: + +- Renaming files: + . +- Front matter: + . + +*** Rename like an expert with ~denote-rename-no-confirm~ +:PROPERTIES: +:CUSTOM_ID: h:8798dd8c-819d-4fda-9865-77d9734da28c +:END: + +By default, the ~denote-rename-file~ command asks for a final +confirmation before carrying out its function. The new user option +~denote-rename-no-confirm~ can be bound to a non-nil value to skip +that step. + +This only applies to ~denote-rename-file~. Other commands that rename +files in bulk never prompt for such confirmation (it would make them +cumbersome to use, plus it is assumed that the user who performs a +batch operation understands the implications). + +*** The ~denote-rename-file~ command prompts for a signature +:PROPERTIES: +:CUSTOM_ID: h:e4e7e3d8-40e3-4f58-a19f-df34ccbfdbbd +:END: + +This command used to only ask for a title and keywords. Now it allows +to use a signature as well. An empty input means that the signature is +ignored. AGAIN, please check with your minibuffer completion UI on how +to input an empty value, otherwise you will not get what you expect. + +*** Rename mutliple files sequentially with ~denote-dired-rename-files~ +:PROPERTIES: +:CUSTOM_ID: h:dcb623aa-fc4d-4d84-80e4-a540b6dbb144 +:END: + +This provides the same interface as ~denote-rename-file~, only it +works over a list of marked Dired files. + +Internally, the prompts for title, keywords, and signature are +improved to display the underlying file that is affected by the +current operation. As the user renames files, the prompts reflect +which one is current. + +*** The name of ~denote-dired-rename-marked-files~ has changed +:PROPERTIES: +:CUSTOM_ID: h:f9a16fc1-840d-400f-a5ae-a7791fac441f +:END: + +It is now called ~denote-dired-rename-marked-files-with-keywords~ to +better communicate what it does. In short, this is a quick way to add +the given keywords to a list of files, converting them to the Denote +file-naming scheme in case they are not already using it. For the full +interactive power, use the aforementioned ~denote-dired-rename-files~. + +*** The ~denote-rename-file-using-front-matter~ can be used without saving its buffer +:PROPERTIES: +:CUSTOM_ID: h:bfc194c2-5980-482a-aa1c-feb4ced992d1 +:END: + +This is now possible because of changes to underlying functions (a +Denote Lisp macro---not to bother you with technicalities). + +Same principle for ~denote-rename-file-using-front-matter~. + +*** The name of ~denote-change-file-type~ has changed +:PROPERTIES: +:CUSTOM_ID: h:3bf4b6c4-8399-4d5d-8df1-6495f5bfc579 +:END: + +It is now called ~denote-change-file-type-and-front-matter~ to avoid +confusion as to whether Denote converts files from one format to +another (there are specialised tools for that). + +*** Renaming a file returns the new file path for programmatic use +:PROPERTIES: +:CUSTOM_ID: h:1d7bffd1-e422-420d-b453-9a36dd8508f7 +:END: + +Thanks to mentalisttraceur for requesting this feature in issue 183 on +the GitHub mirror: . + +** Link to a file with a signature +:PROPERTIES: +:CUSTOM_ID: h:b154ef64-c3b4-4e15-b533-c59d5b2ebf6b +:END: + +The ~denote-link-with-signature~ command prompts for a file that has a +=SIGNATURE= component and links to it. The link's description includes +the text of the signature as well as the title. + +Thanks to Mark Olson for mentioning this idea. It was done in issue +167 on the GitHub mirror: . + +I implemented it live, while also refactoring relevant parts of the +code to be more abstract/reusable: +. + +Thanks to Alan Schmitt for spotting and fixing a regression caused by +the above: +. + +** Renaming GPG or Age encrypted file works as expected +:PROPERTIES: +:CUSTOM_ID: h:9ceaf432-797c-46e5-aaf8-d7180ad66689 +:END: + +Emacs can seamlessly visit a =.gpg= or =.age= file. Denote has nothing +to do with encryption, though it takes care to recognise the +underlying file type and to perform its work accordingly. However, +prior versions of Denote contained a bug in how file extensions were +handled: it would keep the encryption extension but remove the file +type extension before it (so ".org.gpg" would wrongly become ".gpg"). + +Thanks to Jens Östlund for reporting a bug with ~denote-keywords-add~ +on an encrypted file, which prompted me to investigate this further +and fix the issue holistically. This was done in issue 172 on the +GitHub mirror: . + +Interested parties are advised to check the two new public functions, +~denote-get-file-extension~ and ~denote-get-file-extension-sans-encryption~, +for the implementation details. In short, we had a problem with all +operations that needed to retrieve the file extension when that +included an encryption component. + +** The optional ~denote-journal-extras~ +:PROPERTIES: +:CUSTOM_ID: h:54723661-31f8-4cab-9be5-4cab19e44dc7 +:END: + +The manual of Denote has long provided code samples to achieve +particularised results. Among those were snippets to streamline the +use of Denote for journaling. + +To make things even easier for users, we now have the +=denote-journal-extras.el=. It consolidates the rich corpus of +documented snippets into an easy-to-use and formally maintained +package. Thanks to Vedang Manerikar for providing the impetus for this +process. This was done on the mailing list: +. + +The new file is optional. It can be loaded thus: + +#+begin_src emacs-lisp +(require 'denote-journal-extras) +#+end_src + +The main idea is to quickly create journal entries. Check the manual +for the details, including the commands to use and the variables to +configure: . + +Thanks to Kostas Andreadis for working on a comment I had included in +a working state of the code about the inclusion of templates. Kostas +made it possible to use the Denote template prompt (per the +~denote-templates~ user option) as part of the creation of a new +journal entry. This was done in pull request 173 on the GitHub mirror: +. The change is less +than 15 lines and thus Kostas does not need to assign copyright to the +Free Software Foundation. + +Also thanks to TJ Stankus for reporting a case where +~denote-journal-extras-title-format~ did not accept a ~nil~ value (as +it should). This was done in issue 176 on the GitHub mirror: +. + +** The optional ~denote-silo-extras~ +:PROPERTIES: +:CUSTOM_ID: h:618495d2-0c5b-48b4-af88-56f3d969697c +:END: + +This is the same idea as with the =denote-journal-extras.el=: we had +the code in the manual and are now formally distributing it. Thanks +again to Vedang Manerikar for initiating this process. It was done on +the mailing list: +. + +Use this optional feature with: + +#+begin_src emacs-lisp +(require 'denote-silo-extras) +#+end_src + +Consult the manual for the details: +. + +** The infrastructure for unique identifiers is more robust +:PROPERTIES: +:CUSTOM_ID: h:1d538d7f-52e6-4653-b057-c62606752934 +:END: + +For Denote version =2.0.0= I introduced a general scheme intended to +avoid scenaria where duplicate identifiers could be created (thus +breaking a premise of Denote). Jean-Philippe Gagné Guay iterated over +the code to make it more robust and to fix some of the cases I had not +accounted for. This was done in pull request 159 on the GitHub mirror: +. Same idea in pull +request 187: . + +** For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:9031dc82-ab75-438c-a2c8-a1250ae48671 +:END: + +Denote has a clean code base with small and composable functions. This +encourages hackability. Each definition in the source is documented, +while the manual provides an overview of every public symbol. + +- Added :: ~denote-get-file-extension~, + ~denote-get-file-extension-sans-encryption~, + ~denote-keywords-combine~, + ~denote-retrieve-keywords-value-as-string~, + ~denote-title-prompt-current-default~, ~denote-command-prompt~. + +- Refactored :: ~denote-all-files~, ~denote-signature-prompt~, + ~denote-file-prompt~, ~denote-title-prompt~, + ~denote-rewrite-front-matter~. + +Please read their documentation strings for the details. Or check the +manual: . + +** Check out the ~denote-explore~ package by Peter Prevos +:PROPERTIES: +:CUSTOM_ID: h:759d0276-17e8-4461-9ee4-b4d07840dd7a +:END: + +Peter posted this on the mailing list and I asked if it was okay to +mention it in the release notes of Denote. If you have a relevant +announcement to make, consider sending it to our mailing list. + +#+begin_quote +Hi folks, + +I have just updated the denote-explore package: +https://github.com/pprevos/denote-explore + +It does three things: + +1. Summary statistics: Count and visualise keywords and note types +2. Random walks: Generate new ideas using serendipity +3. Network visualisation: Visualise your Denote network of links + +It contains a rudimentary network visualisation function, relying +on the R language. I will need some D3.js expertise to improve the +visualisation. + +There should be a way to generate the basic network structure just +using Elisp and feeding a JSON to D3.js. + +Regards + +P:) +#+end_quote + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:01dc6bb0-53ac-43e1-b12e-484c99a6c2a7 +:END: + +- During this release cycle, I made lots of changes that in one way or + another related to the ~denote-file-prompt~. It was relying on a + =project.el= mechanism that did not allow us to do everything we + needed. I have thus arranged for it to use the standard + ~completing-read~ mechanism. There are subtle differences in + behaviour, though the core idea is the same. This change fixes a few + not-so-obvious bugs. Interested parties are advised to refer to the + message in commit =50d1bbdf1e8ffe0f449f2f5da02f9b70322fff7d=. + +- All commands that use the ~denote~ function internally (i.e. + anything that creates a new note) call the + ~denote-after-new-note-hook~ as part of their work. This hook is + mostly intended for advanced users who want to do something after a + new note is produced. + +- The ~menu-bar-mode~ submenu of Denote is now positioned where it + should be after the "Tools". Thanks to Noboru Ota for the patch: + . + +- The ~menu-bar-mode~ entry of Denote includes the new commands. This + is a nice way to discover more of what Denote can do. + +- The commands ~denote-backlinks-prev~ and ~denote-backlinks-next~ are + only meant to be used inside the Denote backlinks buffer. As such, + they now produce an error when called elsewhere (I wish I could hide + them from =M-x= altogether). + +- The ~denote-extract-keywords-from-front-matter~ always returns a + list, thus avoiding an erroneous case. Thanks to Vedang Manerikar + for fixing the bug: . + +- The =T= in the Denote identifier component now has its own face: + ~denote-faces-time-delimiter~. This is used by the backlinks buffer + and the ~denote-dired-mode~. The idea is to introduce a subtle + distinction between the date and time constituents of the + identifier. Those who want the =T= to be the same colour as the rest + of the identifier, can make the ~denote-faces-time-delimiter~ + inherit the ~denote-faces-date~. For example: + + #+begin_src emacs-lisp + (set-face-attribute 'denote-faces-time-delimiter nil :inherit 'denote-faces-date) + #+end_src + + Thanks to Jean-Charles Bagneris for sending this patch: + . + +- Fixed a ~nil~ file expansion in the function + ~denote--extract-title-from-file-history~. Thanks to ezchi for + bringing this matter to my attention. It was done in issue 166 on + the GitHub mirror: + . + +- A link can be created from inside an ~org-capture~ buffer. This + means that we can call ~denote-link~ (and related) while capturing a + new note with ~org-capture~. Thanks to Peter Smith for reporting the + bug in issue 186 on the GitHub mirror: + . + +- We stopped using ~vc-rename-file~ to rename files. The reason is + that it requires the buffer to be saved, but we do not want that + after modifying the front matter because we want to give the user a + chance to confirm what happened. Thanks to Frédéric Willem for + reporting the problem in issue 185 on the GitHub mirror: + . + +- Thanks to Ivan Sokolov for removing a double negative logic in a + snippet. This was done in pull request 162 on the GitHub mirror: + . + +** Git commits +:PROPERTIES: +:CUSTOM_ID: h:d8f30943-70dd-45fe-8cf1-4c3918152aeb +:END: + +Just an overview of what we did. Every contribution matters. + +#+begin_src +~/Git/Projects/denote $ git shortlog 2.0.0..2.1.0 --summary --numbered + 153 Protesilaos Stavrou + 15 Jean-Philippe Gagné Guay + 5 Vedang Manerikar + 1 Alan Schmitt + 1 Ivan Sokolov + 1 Jean-Charles Bagneris + 1 Kostas Andreadis + 1 Noboru Ota + 1 Peter Prevos +#+end_src + +* Version 2.0.0 on 2023-07-21 +:PROPERTIES: +:CUSTOM_ID: h:3f17bf03-4c47-4410-abf8-1db4a0ac7775 +:END: + +This is the second major version of Denote, close to one year after +its initial release. The video demo I did back then remains relevant, +even though lots of details have changed. + +** Notes have a new optional SIGNATURE field +:PROPERTIES: +:CUSTOM_ID: h:a3a9e14d-4132-47c0-a23c-cb008a141668 +:END: + +It is now possible to create notes that include a =SIGNATURE= field in +their file name. Either use the convenience command ~denote-signature~ +or configure the user option ~denote-prompts~ to affect what the ~denote~ +command should prompt for. + +Signatures are arbitrary strings of characters that enable the user to +further qualify their documents. One possible workflow is to write +relational notes, such that =1a1= is the first extension of another +note with a =1a= signature. + +The design of the =SIGNATURE= field is consistent with the Denote +file-naming scheme. The field separator is the double equals sign +(~==~), while words that comprise the signature are joined together by +a single equals sign. As such, the user can prefix a search with an +equals sign to match words in the =SIGNATURE=, just as they would use +dashes for the =TITLE= and underscores for the =KEYWORDS=. + +[ Read the manual for the technicalities of the Denote file-naming + scheme. This is not limited to "notes": any file can be named + accordingly (I do it with my videos, for example). ] + +Signatures are not included in a file's front matter. This is a +strategic decision to preserve backward compatibility, while not +introducing a feature that has not enjoyed widespread usage. I want +to make signatures behave the same as the rest of the file name +fields, though I am interested to learn how users employ them in their +workflow. + +The signature extension was discussed at length on the GitHub mirror +in issue 115: . +Thanks to Stefan Thesing, Mirko Hernandez, Noboru Ota (nobiot), +Xiaoxing Hu, nbehrnd, Elias Storms, and 101scholar for helping me +reason about this feature, understand its scope, and prototype its +implementation. + +Also thanks to Alfredo Borrás and Jeremy Friesen for discussing with +me the field delimiter of signatures on the mailing list: +. +Thanks to Kai von Fintel for doing the same on the GitHub mirror in +issue 147: . + +Read the original announcement: +. + +As part of the development, I fixed a case where +~denote-rename-file-using-front-matter~ would fail if it could not +find a signature + +The idea is that we want the command to behave the way it always did +when the file has no signature and to preserve the signature when it +is present. + +Thanks to relict for reporting the issue on the mailing list: +. + +** The rename commands avoid creating duplicate identifiers +:PROPERTIES: +:CUSTOM_ID: h:d24645a3-ad02-450c-b3d7-af7802aa0b26 +:END: + +Denote provides commands to rename an existing file to one that +follows the Denote file-naming scheme (videos, PDFs, other text +documents, ...). Check, for example, the ~denote-rename-file~ and +~denote-dired-rename-marked-files~. The idea is to make everything +easier to search. + +In prior versions, these commands could produce duplicate identifiers +if the modification date of the underlying files was the same. Such a +scenario occurs when the files are modified programmatically, as with +the =touch= command or the various =git= operations. + +Denote will now take care to increment the identifier until it becomes +unique within the current scope. + +Thanks to Felipe Balbi for reporting this bug in issue 105 on the +GitHub mirror: . + +Thanks to Vedang Manerikar and Jean-Charles Bagneris for commenting on +this feature on the mailing list: +. + +Thanks to Ashton Wiersdorf for noticing a mistake I made that caused a +regression in ~denote-rename-file~: +. + +*** Optional arguments affect ~denote-dired-rename-marked-files~ +:PROPERTIES: +:CUSTOM_ID: h:6ea998be-83dd-4c67-945c-11011372818f +:END: + +The ~denote-dired-rename-marked-files~ now accepts two optional +arguments. When called interactively, these are interpreted as a +single or double universal prefix argument (=C-u= by default, though +do =M-x where-is= and search for ~universal-argument~). + +The first argument, named =SKIP-FRONT-MATTER-PROMPT=, skips the "yes +or no" prompt requested at the outset of the operation, passing to it +an affirmative response. Thanks to Jay Rajput for asking the question +that inspired me to implement this. It was done in issue 155 on the +GitHub mirror: . + +The second argument, named =NO-UNIQUE-ID-CHECK=, will not perform any +checks for potential duplicate identifiers. The default is to check +for duplicates and increment them such that they become unique. The +reason this optional argument exists is for those who want to speed up +the process, perhaps because they know ahead of time all identifiers +will be unique or do not care about them. + +Thanks to Bruno Boal for refining how the prefix argument is +processed. The patch was sent via a private channel. The change is +small and thus does not require copyright assignment to the Free +Software Foundation. + +** Menu entries help users discover Denote +:PROPERTIES: +:CUSTOM_ID: h:651e5561-f9ce-41f6-bad3-d54ce2dcff04 +:END: + +Users of ~menu-bar-mode~ and/or ~context-menu-mode~ will now find a +submenu with points of entry to Denote. Refer to the publication I +made on my website, as it includes a picture: +. I +will save the thousand words for the following sections. 🙃 + +There is a known issue where the ~menu-bar-mode~ entry is positioned +before the =File= submenu. Apparently, there exists an inelegant way +to place the menu elsewhere, but I am not willing to maintain hacks +for missing functionality. If someone knows a clear way to put the +submenu elsewhere, please contact me: I want it to be after =Tools=. + +Thanks to Kai von Fintel and Noboru Ota (nobiot) for discussing the +placement of the submenu: +. + +** "Link" commands have simpler names +:PROPERTIES: +:CUSTOM_ID: h:acf95a79-3c45-423d-a88f-d6eed7fa5387 +:END: + +Originally, Denote was organised as a collection of several files, +each of which had its own prefix like =denote-dired.el=, and +=denote-link.el=. This arrangement was deemed surplus to requirements +and all core code was consolidated in =denote.el=. An artefact of +that design was the presence of symbols that retained their admittedly +awkward names, like the command ~denote-link-backlinks~ or +~denote-link-add-missing-links~. + +All such commands are deprecated. They are replaced with more +discoverable names. The deprecation is done in such a way that the +old names are aliases for the new ones, but the user is warned not to +rely on them. + +The new names in detail: + +| Old name 🤨 | New name 🤩 | +|-------------------------------------+---------------------------------------------------------------| +| ~denote-link-add-links~ | ~denote-add-links~ | +| ~denote-link-add-missing-links~ | ~denote-add-missing-links~ | +| ~denote-link-backlinks~ | ~denote-backlinks~ | +| ~denote-link-find-file~ | ~denote-find-link~ | +| ~denote-link-insert-link~ | ~denote-insert-link~ (alias for ~denote-link~) | +| ~denote-link-show-backlinks-buffer~ | ~denote-show-backlinks-buffer~ (alias for ~denote-backlinks~) | + +** Denote buffers can have shorter names +:PROPERTIES: +:CUSTOM_ID: h:98f6b10a-ea29-49d1-8d3f-e2f0409f4c8f +:END: + +The Denote file-naming scheme is designed to be a low-tech way of +embedding information in files, making them easier to find. A +downside is that the names are longer than =blah.txt= and so the +default Emacs behaviour is to derive a buffer name from the file name. + +The new optional =denote-rename-buffer.el= provides a minor mode to +automatically rename the buffer of an existing file, such that it +reflects the file's =TITLE= field. Users must enable +~denote-rename-buffer-mode~. + +The renaming procedure is controlled by the user option +~denote-rename-buffer-function~. By default, it provides the means to +rename using (i) the title, (ii) the identifier, or (iii) a custom +function that returns a string. Experienced users can refer to +~denote-rename-buffer-with-title~ to draw inspiration on the design of +such a function. + +Thanks to Morgan Davidson for asking a question that inspired me to +implement this feature. The discussion took place in issue 151 on the +GitHub mirror . + +** Silos work as directory trees +:PROPERTIES: +:CUSTOM_ID: h:113820c4-7a6f-4126-9a44-92bfa59744e2 +:END: + +Denote provides a feature to isolate files in to their own silos, each +of which functions as its own ~denote-directory~ variable. The +technicalities are explained in the manual. Silos have proven to be a +valuable aspect of file management and I have thus expanded their +scope to work as fully fledged directory trees. This means that we no +longer assume a silo to be a flat directory listing, but instead +recognise any subdirectories inside of it. + +Thanks to relict007, Hilde Rhyne, Mirko Hernández, Noboru Ota +(nobiot), Alan Schmitt, hapst3r, and Hilde Rhyne for their +participation in the relevant discussions: + +- +- +- +- (GitHub mirror) +- . + +** Keywords do not accept multiple words by default +:PROPERTIES: +:CUSTOM_ID: h:08f23806-9570-4031-86e4-810b3e93be81 +:END: + +The idea is to have short keywords and then use more than one, if +necessary. We do not want to encourage the habit of long keywords +that become overly specific, while we want to avoid the use of +dashes as delimited in the file name's =KEYWORDS= field. + +Technically, this changes the default value of the user option +~denote-allow-multi-word-keywords~. Users who preferred the old +behaviour can simply toggle it on. + +** Pass arguments to Org capture +:PROPERTIES: +:CUSTOM_ID: h:58ff6dd3-693a-4437-9217-8e876d92c975 +:END: + +Denote is not an extension of Org mode, though it can integrate with +~org-capture~. I now make it possible to design a capture template +that uses specific prompts. Consult the section in the manual titled +"Create note with specific prompts using Org capture". + +Thanks to Aditya Yadav for asking about this in issue 132 on the +GitHub mirror: . + +** Change an existing note's file type +:PROPERTIES: +:CUSTOM_ID: h:e1e874e3-d8ad-4685-aa62-59ad07078db2 +:END: + +The command ~denote-change-file-type~ changes the file type of an +existing note. The available options are those among +~denote-file-type~. Thanks to Jean-Philippe Gagné Guay for the +contribution, which was done in pull request 137 on the GitHub mirror: +. + +** Denote dynamic blocks can now parse ~rx~ notation +:PROPERTIES: +:CUSTOM_ID: h:fe595ee7-8ba6-4ca3-aa66-35aa4e5ca0f5 +:END: + +Denote can leverage the Org feature of "dynamic blocks" to produce +lists of links/backlinks. This is especially useful for metanotes +(read the Denote manual---I document everything for a reason). + +Before, regular expressions were implemented only as strings while now +they can also be written using the ~rx~ notation. Thanks to Mirko +Hernandez for proposing this feature and discussing it with me in +issue 122 on the GitHub mirror: +. + +Thanks to Elias Storms, the author of =denote-org-dblock.el=, for +iterating on this functionality. This was done in pull request 130 on +the GitHub mirror: . + +** Made links to non-note files works as intended +:PROPERTIES: +:CUSTOM_ID: h:431a8952-0d71-4ba6-b6ae-85e5f7d520b9 +:END: + +The function ~denote-get-path-by-id~ is refactored to accept any file +with an identifier. This always was its intended purpose. The user +was always able to create =denote:= Org link types to, for example, +=jpg= files but ~denote-get-path-by-id~ was refusing to resolve the +otherwise valid path. Thanks to user relict007 for reporting the +problem and discussing it with me in issue 135 on the GitHub mirror: +. + +The change was not trivial. It was followed up by a patch from Noboru +Ota (nobiot) which elaborated on the conditionality. Quoting from +commit =9ce9a24=: + +#+begin_quote +fix(denote-get-path-by-id): #135 + +Reference: https://github.com/protesilaos/denote/issues/135 + +This patch change function 'denote-get-path-by-id' to allow for the following: + +- A single ID points to multiple files with different extensions +- Denote needs to find a single file out of the multiple files +- This is not necessarily a user error (export an Org file to an HTML) +- Denote should let user decide their "primary" file extension + +The case the patch is intended to fix goes something like this: + +- You have 20230216__mynotes--tag.org. +- You export it to 20230216__mynotes--tag.html. +- Both files are in denote-directory +- This means you have two files with the same ID with different + extensions denote-link-find-file, denote-link-find-backlink, and xref + integration might find the html file INSTEAD OF the .org file + +This is because html is earlier in the alphabetical order than +org. Because the function uses seq-find, it will find the .html file +first and returns it. +#+end_quote + +** The ~denote-rename-file-using-front-matter~ works with empty keywords +:PROPERTIES: +:CUSTOM_ID: h:b00f228d-7f84-4d84-8d5f-ac90ea6b1065 +:END: + +Keywords are an optional field in the Denote file-naming scheme. +However, an earlier version of the command mentioned in this heading +was considering them mandatory and would refuse to proceed if the +keywords were nil. Thanks to Eduardo Grajeda for fixing this: +. + +The change is within the ~15 line limit and does not require copyright +assignment to the Free Software Foundation. + +** The ~denote-title-prompt~ has its own history +:PROPERTIES: +:CUSTOM_ID: h:91f370f4-9fd1-461b-8ba4-fd9ba2d9c7a8 +:END: + +Denote implements minibuffer histories for all its relevant functions. +This makes it easier for users to retrieve their previous inputs and +to not get irrelevant ones. + +Before, the ~denote-title-prompt~ was not using its own history but +was instead relying on another one that was intended only for file +paths, thus mixing unrelated inputs. + +Thanks to Jonathan Sahar for bringing this matter to my attention. +This was done in issue 144 on the GitHub mirror: +. + +** For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:dcc52671-2127-47e0-9167-003f40ca3a54 +:END: + +*** Made it possible to add predicates for recursive file listing +:PROPERTIES: +:CUSTOM_ID: h:62546ec1-6ec8-41c7-9a18-10a531b534ce +:END: + +The helper function ~denote--directory-all-files-recursively~ accepts +predicates to help speed up its work. + +Thanks to Wade Mealing for reporting the issue about the performance +of the built-in function ~directory-files-recursively~ in large, +nested directories. And thanks to Graham Marlow for the patch, which +was prepared as part of an extended discussion with me: + +- +- +- +- +- +- +- + +*** New public symbols +:PROPERTIES: +:CUSTOM_ID: h:ed723274-a78e-4cfd-9655-c3bfe0fb1e68 +:END: + +The following are now public symbols that we commit to support and +document henceforth: + ++ Function ~denote-file-type-extensions~ :: Return all file type + extensions in ~denote-file-types~. + ++ Variable ~denote-encryption-file-extensions~ :: List of strings + specifying file extensions for encryption. + ++ Function ~denote-file-type-extensions-with-encryption~ :: Derive + ~denote-file-type-extensions~ plus ~denote-encryption-file-extensions~. + ++ Function ~denote-link-return-links~ :: Return list of links in + current or optional =FILE=. Also see ~denote-link-return-backlinks~. + ++ Function ~denote-link-return-backlinks~ :: Return list of links in + current or optional =FILE=. Also see ~denote-link-return-links~. + ++ Function ~denote-rewrite-front-matter~ :: Rewrite front matter of + note after ~denote-rename-file~ (or related) The =FILE=, =TITLE=, + =KEYWORDS=, and =FILE-TYPE= arguments are given by the renaming + command and are used to construct new front matter values if + appropriate. + ++ Function ~denote-rewrite-keywords~ :: Rewrite =KEYWORDS= in =FILE= + outright according to =FILE-TYPE=. Do the same as + ~denote-rewrite-front-matter~ for keywords, but do not ask for + confirmation. This is for use in ~denote-keywords-add~, + ~denote-keywords-remove~, ~denote-dired-rename-marked-files~, or + related. + +I am publicising the ~denote-link-return-links~ and its counterpart in +response to the mailing list thread started by relict007: +. +relict007 is the developer of the ~denote-cache~ package (in +progress): . + +Similarly, the ~denote-rewrite-keywords~ is made public upon the +request of Alan Schmitt: +. + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:918087e6-8cd5-4d4f-a11a-b465dcbd9fe3 +:END: + +- Revised ~denote-link-return-{links,backlinks}~ to not produce a + ~user-error~. The errors are reserved for the interactive + functions. The others are for developers. Thanks to Elias Storms for + bringing this matter to my attention: + . + +- Refrained from trying to find forward links in non-text-files. If a + file extension is not in ~denote-file-types~, we have no way of + parsing or finding outgoing links in it. This change checks for the + file extension early on in 'when-let*' block and avoids opening the + file which is a relatively costly operation (and would fail finding + links anyway). Thanks to relict007 for the patch. This was done on + the mailing list: + + The change is small and thus does not require copyright assignment + to the Free Software Foundation. + +- Explained how to troubleshoot Denote. Refer to the section in the + manual titled "Troubleshoot Denote in a pristine environment." + While this is about Denote, the skills apply to all Emacs packages. + +- Ensured backlinks get correct ~denote-directory~ path. The + backlinks buffer will now get the correct path when it is generated + inside a silo. This is related to issue 129 reported by hapst3r on + the GitHub mirror: . + The change is necessary because =.dir-locals.el= do not work for + buffers, so we must get the value from the file that calls + ~denote-link-backlinks~. + +- Added missing underscore from examples in exporting section. Thanks + to Peter Prevos for bringing this matter to my attention: + . + +- Made the command ~denote-open-or-create~ work with an empty + ~denote-directory~. The ~denote-file-prompt~ would throw an error + before. The correct behaviour is to proceed to the "Create" phase + if the ~denote-directory~ is empty. Thanks to user drcxd for + reporting the bug in issue 131 on the GitHub mirror and for testing + my sample code: . + +- Documented how to use tree-based file prompt on demand. This is my + solution to a request made by Mirko Hernandez on the possible use of + the old Denote file prompt. It is better not to introduce a user + option for this case, nor to keep multiple variants of the + ~denote-file-prompt~ in denote.el, as we want to keep things simple. + Mirko's feedback was provided in issue 121 on the GitHub mirror: + . + +- Added the variable ~denote-user-enforced-denote-directory~. This is + intended for users who write custom code to extend Denote. The + value of this variable should be ~let~ bound around calls to the + function ~denote-directory~, thus overriding its return value. This + was discussed on the mailing list and then introduced by Vedang + Manerikar in commit =977c757=, with further changes by me in + =20ddc97=: . + Vedang has assigned copyright to the Free Software Foundation. + +- Fixed ~my-denote-org-extract-subtree~ section of the documentation. + This is part of some sample code that is not part of =denote.el=, + but we provide as a convenience/inspiration for interested parties. + + The provided function did not work correctly. + + 1. Tags are extracted before deleting the region from the source file. + 2. The function ~org-end-of-subtree~ is called to calculate the + point we should delete up to. The previously used function + ~org-entry-end-position~ ends at the first sub-heading under the + tree, which is not what we want. Instead, we want to cut the + whole subtree. + 3. The date information available in the subtree is retained. We + look for three common places for this information: the =CREATED= + or =DATE= properties in the =PROPERTIES= drawer, and the =CLOSED= + cookie at the element level itself. + + Thanks to Vedang Manerikar for the contribution: + + +- Removed the dependency on the built-in ~xdg~ library and updated the + default value of the user option ~denote-directory~. The reason is + that XDG is a Linux standard that does not work on other operating + systems, according to private feedback I received. + +- Fixed a regression for =M-p= (~previous-history-element~) in "do or + create" commands. Read the doc string of the commands + ~denote-open-or-create~ or ~denote-link-or-create~ for how this is + supposed to work. In short: + + - Invoke the "do or create" command. + - Type something that does not match a file. + - In the following title prompt, hit =M-p= to bring back the last input. + + I realised there was a regression when I read issue 152 on the + GitHub mirror, which was created by user "ustcpxy": + . The issue is + about skipping the file title prompt. + +- Simplified the internal ~denote--buffer-file-names~. Thanks to Adam + Růžička for noting that my change was not compatible with older + Emacs versions, and for preparing the change. This was discussed in + pull request 158 on the GitHub mirror, with my suggestion to not use + ~seq-filter~ as it affected the return value: + . The change is + below the 15 line limit, meaning that Adam does have to assign + copyright to the Free Software Foundation. + +- Documented custom code in the manual on how to interactively select + a silo. I am providing this in response to a request from GitHub + user rbenit68. The discussion took place in issue 127 on the GitHub + mirror, with the participation of Mirko Hernandez: + . The custom code + I provide is the expanded version of an idea put forth by Mirko, to + whom I am thankful. + +- Fixed an outdated reference in the ~denote-file-types~ doc string. + Thanks to user doolio for spotting the error and reporting it in + issue 139 on the GitHub mirror: + . + +- Cited in the manual's section "Publications about Denote" an article + by Mohamed Suliman titled /Managing a bibliography of BiBTeX entries + with Denote/ (2022-12-20): + . + If you have published something related to Denote, please let me + know and I will add to the list. + +- Cited the essay by Summer Emacs titled /An explanation of how I use + Emacs/ (2023-05-04): + + +- Cited the video series by Stefan Thesing titled /Denote as a + Zettelkasten/: . + +- Added link to Karl Voit's work in the manual's section "Alternative + implementations and further reading." Thanks to Norwid Behrnd for + the contribution in pull request 123 on the GitHub mirror: + . + +- Fixed the broken link to jao's blog. Thanks to Tomasz Hołubowicz + for the contribution, which was done in pull request 145 on the + GitHub mirror: . + +- Authored lots of other ancillary changes/features to the code base + or the manual (yes, this change log is how I "cut the long story + short"). + +* Version 1.2.0 on 2022-12-12 +:PROPERTIES: +:CUSTOM_ID: h:92478a05-4a69-413c-8d95-1dacbcf6af2c +:END: + +** Denote now requires Emacs version 28.1 or higher +:PROPERTIES: +:CUSTOM_ID: h:bc0e173a-3b9f-427c-9fb0-d435a5ef127e +:END: + +With the help of Noboru Ota (nobiot), we realised that Denote was +broken on Emacs 27 for quite a while. The fact that we received no +feedback about it suggests that this change is the best course of +action going forward. Discussion: + + +Emacs 27 lacks certain Xref facilities that we need for the +backlinking facility. It was holding us back for no good reason, +while also adding to the maintenance burden. + +If you are using Denote on Emacs 27 and things are working for you, +there is no need to update the package. Do it when you also upgrade +Emacs to a newer version. + +** Display context in backlinks' buffer +:PROPERTIES: +:CUSTOM_ID: h:dafbdbae-36f1-487a-94c8-2762568a766e +:END: + +By default, the generic backlinks' buffer, which can be displayed with +the command ~denote-link-backlinks~ (alias ~denote-link-show-backlinks-buffer~), +only shows the file names of the linked notes. + +We have made it possible to produce a more informative view by showing +the context of the link and also listing all links per file. This is +done by setting the user option ~denote-backlinks-show-context~ to a +non-nil value. + +To illustrate the difference, this is the default backlinks' buffer: + +#+begin_example +Backlinks to "On being honest" (20220614T130812) +------------------------------------------------ + +20220614T145606--let-this-glance-become-a-stare__journal.txt +20220616T182958--feeling-butterflies-in-your-stomach__journal.txt +#+end_example + +And this is the one with ~denote-backlinks-show-context~ enabled: + +#+begin_example +Backlinks to "On being honest" (20220614T130812) +------------------------------------------------ + +20220614T145606--let-this-glance-become-a-stare__journal.txt +37: growing into it: [[denote:20220614T130812][On being honest]]. +64: As I said in [[denote:20220614T130812][On being honest]] I have never +20220616T182958--feeling-butterflies-in-your-stomach__journal.txt +62: indifference. In [[denote:20220614T130812][On being honest]] I alluded +#+end_example + +Granted, here we show plain text though in Emacs the results have the +appropriate colours of the active theme and are easier to read. + +Thanks to Noboru Ota (nobiot) for implementing this feature. We +discussed it at length on the mailing list: +. + +Noboru has assigned copyright to the Free Software Foundation. + +** Dynamic Org blocks for lists of Denote links +:PROPERTIES: +:CUSTOM_ID: h:f7904a57-22c0-446f-b7e3-7a736332002c +:END: + +Denote now includes the ~denote-org-dblock~ library. Activate it +thus: + +#+begin_src emacs-lisp +;; Register Denote's Org dynamic blocks +(require 'denote-org-dblock) +#+end_src + +A dynamic block gets its contents by evaluating a given function, +depending on the type of block. The type of block and its parameters +are stated in the opening =#+BEGIN= line of the block. Typing =C-c +C-c= with point on that line runs the function, with the given +arguments, and populates the block's contents accordingly. + +What Denote has is ways to write blocks that produce a list of links +matching a given regular expression while conforming with some other +parameters. The manual explains how to use this powerful feature +(which is necessarily specific to the Org file type): +. + +Thanks to Elias Storms for authoring ~denote-org-dblock~ and for +discussing this issue at length with me on the mailing list: +. + +Elias has assigned copyright to the Free Software Foundation. + +** Integration with the built-in project.el and xref.el libraries +:PROPERTIES: +:CUSTOM_ID: h:e8a7d08c-cdf0-4207-92c1-391415b8371f +:END: + +Denote was already using Xref internally but has now gained more +capabilities which help it find files more effectively. With the help +of Emacs' standard project library, all file-related prompts (e.g. to +add a link) search all items in the ~denote-directory~ regardless of +whether the user is in a subdirectory or not. + +All Denote commands benefit from this refactoring. One such request +to "Make ~denote-open-or-create~ work better across subfolders" was +made in issue 114 on the GitHub mirror: +. + +Thanks to Noboru Ota (nobiot) for introducing this feature together +with a new system of "modules" for incorporating additional built-in +functionality: + +- +- + +I will not document the new user option ~denote-modules~ right now as +my ongoing job search prevented me from exploring the full potential +of this feature. I promise to do it for the next version of Denote +and update the manual accordingly. Nevertheless, the doc string of +~denote-modules~ already provides all one needs to get started. + +** Re-use last input in "do or create" commands +:PROPERTIES: +:CUSTOM_ID: h:5a003d44-7ad0-4c92-b908-ec7cf016b2dd +:END: + +The commands ~denote-open-or-create~, ~denote-link-or-create~ first +prompt for an existing note. If they find it, they act on it, +otherwise they prompt for the creation of a new note to operate on. + +At the first prompt, it is common to use regular expressions and +out-of-order pattern matching (such as with the ~orderless~ package), +so the input can be something like =_test ^2022 some title=, which we +obviously don't want to automatically reuse as the new note's actual +title. + +To this end, and to accommodate all workflows, we leverage Emacs' +minibuffer history to make the last input accessible with =M-p= at the +minibuffer prompt (=M-x previous-history-element=). The text is +available for further editing before it is submitted as the new note's +title. Simple, effective, and flexible! + +Thanks to Guo Yong for starting the discussion that led me to this +improvement: +. + +** Add support for any file type +:PROPERTIES: +:CUSTOM_ID: h:e73a4e76-6c00-4691-8893-8f885c26f306 +:END: + +Denote provides the user option ~denote-file-type~ which specifies the +file type to use for new notes. Options include Org mode (the +default), Markdown+YAML, Markdown+TOML, and plain text. Furthermore, +there exists the convenience command ~denote-type~ (alias +~denote-create-note-using-type~) which prompts for a file type to use +when creating a new note (I normally write in plain text, but +sometimes switch to Org or Markdown). + +The variable ~denote-file-types~ (which is NOT a user option) +specifies all the parameters of what a "file type" means, such as how +to format its front matter, what style of date+time to use, which file +type extension to write, how to rename the file, what style of link to +apply, and so on. Advanced users can now edit this variable to either +register new file types or redefine the behaviour of existing ones. +Read this comprehensive guide on how to do it: +. + +I repeat: this is for advanced users or, anyhow, for those who are +prepared to maintain some custom code in their setup. The guide is +accessible though and I am always willing to help anyone in need of +assistance. + +A relevant request for such a feature can be found in issue 86 on the +GitHub mirror: . + +The ~denote-file-types~ were introduced by Jean-Philippe Gagné Guay in +pull request 89 at the GitHub mirror and were part of Denote version +0.6.0: . I have made +lots of changes since then to make all parts of Denote work with it +and to parameterise its various facets. + +** Exclude certain directories from all operations +:PROPERTIES: +:CUSTOM_ID: h:04f42aab-d8fe-4c4a-b865-3bb0655e2631 +:END: + +The user option ~denote-excluded-directories-regexp~ instructs all +Denote functions that read or check file/directory names to omit +directories that match the given regular expression. The regexp needs +to match only the name of the directory, not its full path. + +Affected operations include file prompts and functions that return the +available files in the ~denote-directory~. File prompts are used by +several commands, such as ~denote-link~ and ~denote-subdirectory~. +Functions that check for files include ~denote-directory-files~ and +~denote-directory-subdirectories~. + +Thanks to Graham Marlow for the contribution which was done in pull +request 112 on the GitHub mirror: +. + +The original contribution, with the subsequent tweaks I made to it, is +within the eligible line count and thus does not require copyright +assignment to the Free Software Foundation. + +** Exclude certain keywords from being inferred +:PROPERTIES: +:CUSTOM_ID: h:226ba85e-1f5e-45f5-956a-f5e8a95c397e +:END: + +The user option ~denote-excluded-keywords-regexp~ omits keywords that +match a regular expression from the list of inferred keywords. + +Keywords are inferred from file names and provided at relevant prompts +as completion candidates when the user option ~denote-infer-keywords~ +is non-nil. + +Thanks to Stefan Thesing for proposing this idea in issue 115 on the +GitHub mirror: . + +[ Other people participate in that thread and there may be something + more coming out of it. ] + +** Use the ~citar-denote~ package for bibliography notes +:PROPERTIES: +:CUSTOM_ID: h:ff16633f-5fb8-4935-9e2f-044ec998d3f7 +:END: + +Peter Prevos has produced the ~citar-denote~ package which makes it +possible to write notes on BibTeX entries with the help of the ~citar~ +package. These notes have the citation's unique key associated with +them in the file's front matter. They also get a configurable keyword +in their file name, making it easy to find them in Dired and/or +retrieve them with the various Denote methods. + +With ~citar-denote~, the user leverages standard minibuffer completion +mechanisms (e.g. with the help of the ~vertico~ and ~embark~ packages) +to manage bibliographic notes and access those notes with ease. The +package's documentation covers the details: . + +Thanks to Peter Prevos for developing this package and for mentioning +it on the Denote mailing list: +. + +** New functions and variables for developers +:PROPERTIES: +:CUSTOM_ID: h:5cc2076d-d4d2-45be-b28e-9ec67eca82b4 +:END: + +Developers or users who maintain custom code now have access to: + ++ Function ~denote-keywords-sort~ ++ Function ~denote-keywords-prompt~ + +Plus all the following which are related to the aforementioned ~denote-file-types~: + ++ Variable ~denote-org-link-format~ ++ Variable ~denote-md-link-format~ ++ Variable ~denote-id-only-link-format~ ++ Variable ~denote-org-link-in-context-regexp~ ++ Variable ~denote-md-link-in-context-regexp~ ++ Variable ~denote-id-only-link-in-context-regexp~ ++ Function ~denote-date-org-timestamp~ ++ Function ~denote-date-rfc3339~ ++ Function ~denote-date-iso-8601~ + +Again, users can implement support for ANY FILE TYPE and use it to +write notes in, either as their default choice or on-demand. If +anything, this highlights the flexibility of Denote. + +** Miscellaneous +:PROPERTIES: +:CUSTOM_ID: h:acbb0cf7-ad17-495e-85d2-821cbbfc3158 +:END: + ++ Added the ~denote-keywords-sort~ function. The intent is to + abstract the task of sorting the keywords. Before, it was handled + by the ~denote-keywords-prompt~, which meant that keywords were not + sorted when the ~denote~ function was called from Lisp. Thanks to + Florian for bringing this matter to my attention, providing relevant + feedback, and fixing an omission of mine in ~denote-rename-file~: + . + ++ Expanded the manual's entry on directory "silos" to include more + code examples. Thanks to Viktor Haag for asking a question on the + mailing list that inspired me to produce this entry: + . + ++ Included a section in the manual with a non-exhaustive list of + references to publications about Denote. As of this writing, it + includes entries from David Wilson (SystemCrafters), Jack Baty, + Jeremy Friesen, and Peter Prevos. If you have an article about + Denote, please contact me about it directly or on the Denote mailing + list and I will add it to the manual. + ++ Tweaked how Org's HTML export produces links in order to avoid + broken subdirectory paths. Thanks to Thibaut Benjamin for the + contribution, which was done in pull request 116 on the GitHub + mirror: . + + The change concerns a single line and thus Thibaut requires no + copyright assignment to the Free Software Foundation. + ++ Expanded the manual where necessary. + +* Version 1.1.0 on 2022-10-20 +:PROPERTIES: +:CUSTOM_ID: h:8e0f536a-ab3b-4cab-82f7-529bc0e40dbd +:END: + +** New commands or refinements to common use-cases +:PROPERTIES: +:CUSTOM_ID: h:5665e7ec-4f3a-4de3-8cb0-63d25a0db8c1 +:END: + ++ The ~denote-link-add-missing-links~ is a companion to what we + already provide to produce a list of links to Denote files matching + a regular expression (the ~denote-link-add-links~). This new + command adds links that are not already present in the current file. + So if you have a metanote that references, say, your journal entries + but have not updated it in a month, you can revisit the metanote, + invoke ~denote-link-add-missing-links~, and then type the search + terms (e.g. =_journal=) to include what remains. + + Thanks to Elias Storms for the initial contribution, which was done + in pull request 108 on the GitHub mirror: + . + + Elias has assigned copyright to the Free Software Foundation. It is + required for changes that exceed 15 lines in total. + ++ The ~denote-link-find-backlink~ provides a minibuffer interface that + shows all backlinks to the current note. It complements the + existing ~denote-link-backlinks~ command (which also has the alias + ~denote-link-show-backlinks-buffer~). Each command has its own + niche: the minibuffer lets the user leverage powerful pattern + matching styles, such as those provided by the =orderless= package, + while the bespoke buffer provides an easy overview of what links to + the current note. + + Thanks to Elias Storms for the original patch: + . + ++ The ~denote-keywords-add~ and ~denote-keywords-remove~ are two + commands that interactively operate on the current note's front + matter to add or remove keywords. They use the familiar keywords' + prompt which means, among others, that they can read more than one + keyword at a time. To specify multiple keywords, separate each + input with a comma (or whatever the value of ~crm-separator~ is, + which should be a comma unless something out-of-the-ordinary is in + force). + + Thanks to Elias Storms for the original patch, which was done as + part of a discussion on the mailing list and then iterated on: + . + ++ The ~denote-link~ command will now recognise an active region and + use its text as the description of the inserted link. The default + behaviour is to use the file's title from its front matter or file + name. Thanks to Charanjit Singh for the original contribution, + which was done as part of pull request 109 on the GitHub mirror: + . A subsequent + tweak was implemented in pull request 110, following a discussion + with me: . + + Charanjit's contribution is below the ~15 line threshold and thus + does not require copyright assignment to the Free Software + Foundation. + ++ The renaming operations are now aware of the underlying version + control system and will use the appropriate command when a VCS is + available. In practice, renaming a file under, say, Git will + register it as a "rename" instead of two separate actions of + deletion and addition. + + Thanks to Florian for the patch. It was discussed on the mailing + list and then underwent some changes: + . + ++ The ~denote-rename-file-using-front-matter~ no longer fails to carry + out its intended task when the front matter has no keywords. If no + keywords are available, this is interpreted as a request to remove + the KEYWORDS component of the file name. This was always + technically possible and could be achieved with various permutations + of the user option ~denote-prompts~ (as explained in its doc string + or the manual). Denote only needs an identifier in the file name to + establish unique links (although I strongly encourage you to stick + to the standard file-naming scheme as it is informative, reliable, + and can work even if you access your data without Emacs). + +** For more advanced use-cases +:PROPERTIES: +:CUSTOM_ID: h:505c84dd-2959-4bd4-8af4-78d75592a6d5 +:END: + ++ The variable ~denote-file-types~ has been tweaked to respond + directly to changes in its value done with ~setq~. Thanks to Noboru + Ota for the patch: . + + Noboru has assigned copyright to the Free Software Foundation. + ++ The =:front-matter= property of the ~denote-file-types~ now accepts + a nil value. Denote could always work without front matter, but + this was not implemented flexibly in the ~denote-file-types~. + Thanks to Noboru Ota (nobiot) for pointing this out on the mailing + list: . + ++ The ~denote-file-prompt~ function now reads an optional + =INITIAL-TEXT= argument. This is a string that prepopulates the + minibuffer. It is useful for custom commands the user may have + where, for example, there is a need to automatically filter to + entries matching =_journal=. Thanks to Alan Schmitt for suggesting + the idea: . + ++ The ~denote-rename-file-using-front-matter~ accepts an optional + =AUTO-CONFIRM= argument. It can either be passed interactively or + via Lisp. The doc string (or the manual) explains the details. + Thanks to Elias Storms for the initial patch: + . + ++ The ~denote-prompt-for-date-return-id~ function uses the familiar + ~denote-date-prompt~ and returns the appropriate identifier. It is + used internally by some of our function, but we also provide it for + anyone who wants to write their own custom code. + ++ The ~denote-retrieve-or-create-file-identifier~ function reads and + option =DATE= argument to its mandatory =FILE= argument. If =FILE= + does not have an identifier and optional =DATE= is non-nil, the + function invokes the ~denote-prompt-for-date-return-id~, as + mentioned above. + ++ The ~denote-rename-file~ command accepts an optional =DATE= + argument. It functionally does what is described right above, with + the exception that this is for an interactive function (a + "command"). Read the detailed doc string or the manual for + everything that pertains to this powerful command. + + Thanks to Florian for suggesting the idea on the mailing list: + . + ++ The ~denote-directory-text-only-files~ function filters the + ~denote-directory-files~ to only return a list of text files. This + leaves out, say, mp3 files. The function is used internally, though + it may also prove useful in custom user code. + +** Miscellaneous refinements +:PROPERTIES: +:CUSTOM_ID: h:0531047f-ef15-412e-b265-886c55526d57 +:END: + ++ Implemented a ~revert-buffer-function~ for the backlinks' buffer, + which is produced by the command ~denote-link-backlinks~. This + revert function is what the =g= key invokes with the default key + bindings (the command is ~revert-buffer~). It produces the buffer + anew, updating the list of backlinks accordingly. + ++ Documented how to speed up the creation of the backlinks' buffer. + As this depends on the built-in =xref= library, the change is done + by specifying the value of the user option ~xref-search-program~ in + Emacs 28 or higher. For example: + + #+begin_src emacs-lisp + (setq xref-search-program 'ripgrep) + #+end_src + + For something more elaborate: + + #+begin_src emacs-lisp + ;; Prefer ripgrep, then ugrep, and fall back to regular grep. + (setq xref-search-program + (cond + ((or (executable-find "ripgrep") + (executable-find "rg")) + 'ripgrep) + ((executable-find "ugrep") + 'ugrep) + (t + 'grep))) + #+end_src + ++ Removed some minor duplication of effort in how the buttonisation of + links is done (what makes them clickable). + ++ Made refinements to the definition of functions such as + ~denote-link-add-links~. There should be no noticeable change for + users, though this shows we care about code quality. + ++ With Eshel Yaron, we tried to remove the empty indices for functions + and variables from the HTML version of the manual. These indices + are useful in the Info version, which can be accessed directly from + Emacs when the =denote= package is installed (for example, evaluate + =(info "(denote) Top")=), but they do not work with HTML. Alas, + what we tried to do did not work. Maybe Org has a way to control + what is exported where. We shall see. At any rate, thanks to Eshel + for the effort: . + ++ All code that integrates the =denote:= custom hyperlink type with + Org's link facility is now assigned =autoload= cookies. These are + done to ensure that =denote= is loaded and is available in cases + where Org needs to access a =denote:= link at some early stage + (e.g. at startup before using Denote). Thanks to Sven Seebeck for + reporting the problem: . + Although Sven could not reproduce a bug reliably, I believe this + prevents such an eventuality. + ++ Expanded or otherwise updated the manual to account for all of the + above, where appropriate. + +* Version 1.0.0 on 2022-09-30 +:PROPERTIES: +:CUSTOM_ID: h:053975d7-3fe2-49e5-96a0-336483e5861c +:END: + +This is the first major release of Denote. A part of the changes +documented herein is for advanced users or developers who wish to +extend Denote with their custom code. Though we first cover what +applies to everyone. + +** Changes for all users +:PROPERTIES: +:CUSTOM_ID: h:25692d4f-08da-4938-a81e-54070d91f51a +:END: + ++ The custom Org hyperlink type of =denote:= can be visited from + outside the ~denote-directory~. We now provide the necessary glue + code that Org needs to store these =denote:= links. Storing them + can be done with an ~org-capture~ template or via the command + ~org-store-link~. Use this to, for example, capture a TODO that + references one of your notes. + + =denote:= links work for as long as the referenced file is somewhere + in the ~denote-directory~ or one of its subdirectories. + + Thanks to Marc Fargas for the contribution. Marc did not need to + assign copyright to the Free Software Foundation, as the patch was + within the ~15 line limit that is permissible. + + The contribution was discussed on the mailing list: + . A prior + exchange took place in issue 104 over at the GitHub mirror: + . + + Some further tweaks were made to the relevant function. Thanks to + Elias Storms for reporting on the mailing list a bug which revealed + a regression I introduced to the Org link storing mechanism: + . + ++ Following from above, the command ~denote-link-find-file~ finds + files reliably, regardless of where the link is stored. All it + needs is for the target file to be inside the ~denote-directory~. + + I discovered this while exchanging views with Marc Fargas regarding + the aforementioned patch: . + ++ The command ~denote-link-buttonize-buffer~, which "buttonizes" + =denote:= links in plain text and Markdown files, now performs its + task regardless of where the current file is stored. Those links + work for as long as the file they reference is somewhere inside the + ~denote-directory~. + ++ The commands ~denote-link-after-creating~, ~denote-link-or-create~ + provide a convenience for users who need to create link to notes + that may not exist yet. The idea is that one is expounding on a + given topic and wants to create a link to a relevant issue. They + are not sure if they have written anything about it yet, so they + invoke the relevant command. Consult their doc strings or read the + manual: . + + Thanks to user sienic for suggesting the idea and for testing the + prototypes. And thanks to Juanjo Presa for participating in the + discussion to share the view that this functionality should be part of + denote.el. This happened in issue 96 over at the GitHub mirror: + . + ++ The command ~denote-open-or-create~ offers the convenience of + visiting a file, if it exists, else prompting for its creation. + Thanks to Alan Schmitt for the contribution. The patch was sent on + the mailing list: . + It is within the limit of what is allowed without assigning + copyright to the Free Software Foundation, though Alan has done the + relevant paperwork. + ++ The manual expands on two sections: (1) Variants of + ~denote-open-or-create~, (2) Variants of ~denote-link-or-create~. + They show how one can use the above "do or create" commands with + different permutations of the Denote prompts for new note creation. + ++ The manual includes a section titled "Create a note with the + region's contents". Quote: + + #+begin_quote + Sometimes it makes sense to gather notes in a single file and later + review it to make multiple notes out of it. With the following + code, the user marks a region and then invokes the command + ~my-denote-create-new-note-from-region~: it prompts for a title and + keywords and then uses the region's contents to fill in the newly + created note. + #+end_quote + + This is not part of denote.el, though we provide it in the manual + for users that may need it. Thanks to sundar bp for suggesting the + idea. This was done via a private channel and the information is + shared with permission. + ++ The manual has another entry titled "Split an Org subtree into its + own note", which is similar to the above idea of using the region's + contents but has some extra niceties provided by Org. Quote: + + #+begin_quote + With Org files in particular, it is common to have nested headings which + could be split off into their own standalone notes. In Org parlance an + entry with all its subheadings is a "subtree". With the following code, + the user places the point inside the heading they want to split off and + invokes the command ~my-denote-split-org-subtree~. It will create a + note using the heading's text and tags for the new file. The contents + of the subtree become the contents of the new note and are removed from + the old one. + #+end_quote + + Thanks to Sven Seebeck for suggesting the idea and for testing my + prototypes. This information is shared with permission, as it was + provided via a private channel. + ++ The manual describes how a user can leverage the built-in + ~dired-virtual-mode~ to perform arbitrary sorting of their list of + notes. It also includes code for Eshell to quickly "export" a + command's output into a dedicated buffer (which can then be used to + derive a "virtual" Dired). Thanks to Yi Liu for asking the question + that inspired this entry: + . + ++ The ~denote-faces-broken-link~ has been removed. It was used for + Org links. The idea was to apply a different style if the link was + broken. However, the way fontification works means that there may + be a performance penalty as Org tries to check again and again if + the link is broken or note. As =denote:= links are robust (unless + the user tries to break them), this penalty is unacceptable. Thanks + to Peter Prevos for reporting the issue and discussing it with me on + the mailing list: + . + ++ The "denote" group in Custom UI buffers now provides a link to the + Info manual that is shipped with the package. To read the manual, + evaluate =(info "(denote) Top")=. Else visit the official web page: + . + ++ Fixed a case where an internal check for a note would throw an error + if the buffer was not visiting a file. Thanks to Hilde Rhyne was + the patch: it is below the ~15 line threshold and thus does not + require copyright assignment to the Free Software Foundation. The + issue was discussed on the mailing list and was pushed to users as + version =0.6.1=: + . + ++ When linking to a file that has no front matter, Denote tries to use + the TITLE component of the file name (per our file-naming scheme) as + the link's descriptive text. We now make this look a bit better, by + capitalising only the first letter while dehyphenating the text, + converting =this-is-a-test= to =This is a test=. Before, we would + capitalise all words. Thanks to Clemens Radermacher for the patch. + It was sent via a private channel. Clemens has assigned copyright + to the Free Software Foundation. + +** Changes for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:165cd056-5e27-4536-b8ac-57f88c927a43 +:END: + +Lots of functions and variables which once were for "private" use (the +presence of double hyphens in the symbol) are now made public. +Concretely this means that they no longer have double hyphens in their +name and we pledge to support them henceforth. "Support" means that +we (i) consider them stable, (ii) document them properly, (iii) will +record any changes made to them such as in a change log, a blog post +on my website, and via ~make-obsolete~. + +The manual provides a complete reference of what is on offer. The +section is titled "For developers or advanced users": +. + +Normally, we do not support private forms and can delete/modify them +without notice. However, I decided to write obsoletion aliases for +all forms I made public or otherwise revised, in an effort not to +break any existing custom code. The following table covers all +obsolete symbols and their new counterparts. PLEASE UPDATE YOUR CODE +as those aliases will be removed in the near future. + +| Index | Old symbol | New symbol | +|-------+------------------------------------------------+---------------------------------------------------| +| 1 | denote--id-format | denote-id-format | +| 2 | denote--id-regexp | denote-id-regexp | +| 3 | denote--title-regexp | denote-title-regexp | +| 4 | denote--keywords-regexp | denote-keywords-regexp | +| 5 | denote--punctuation-regexp | denote-excluded-punctuation-regexp | +| 6 | denote-punctuation-excluded-extra-regexp | denote-excluded-punctuation-extra-regexp | +| 7 | denote--sluggify | denote-sluggify | +| 8 | denote--sluggify-and-join | denote-sluggify-and-join | +| 9 | denote--sluggify-keywords | denote-sluggify-keywords | +| 10 | denote--desluggify | denote-desluggify | +| 11 | denote--only-note-p | denote-file-is-note-p | +| 12 | denote--file-has-identifier-p | denote-file-has-identifier-p | +| 13 | denote--file-supported-extension-p | denote-file-has-supported-extension-p | +| 14 | denote--writable-and-supported-p | denote-file-is-writable-and-supported-p | +| 15 | denote--file-name-relative-to-denote-directory | denote-get-file-name-relative-to-denote-directory | +| 16 | denote-link--id-from-string | denote-extract-id-from-string | +| 17 | denote--directory-files | denote-directory-files | +| 18 | denote--subdirs | denote-directory-subdirectories | +| 19 | denote--get-note-path-by-id | denote-get-path-by-id | +| 20 | denote--directory-files-matching-regexp | denote-directory-files-matching-regexp | +| 21 | denote--retrieve-read-file-prompt | denote-file-prompt | +| 22 | denote--extract-keywords-from-path | denote-extract-keywords-from-path | +| 23 | denote--keywords-prompt | denote-keywords-prompt | +| 24 | denote--retrieve-filename-identifier | denote-retrieve-filename-identifier | +| 25 | denote--file-name-id | denote-retrieve-or-create-file-identifier | +| 26 | denote--retrieve-filename-title | denote-retrieve-filename-title | +| 27 | denote--retrieve-title-value | denote-retrieve-title-value | +| 28 | denote--retrieve-title-line | denote-retrieve-title-line | +| 29 | denote--retrieve-keywords-value | denote-retrieve-keywords-value | +| 30 | denote--retrieve-keywords-line | denote-retrieve-keywords-line | +| 31 | denote--format-file | denote-format-file-name | +| 32 | denote--barf-duplicate-id | denote-barf-duplicate-id | +| 33 | denote--title-prompt | denote-title-prompt | +| 34 | denote--file-type-prompt | denote-file-type-prompt | +| 35 | denote--date-prompt | denote-date-prompt | +| 36 | denote--subdirs-prompt | denote-subdirectory-prompt | +| 37 | denote--template-prompt | denote-template-prompt | +| 38 | denote--filetype-heuristics | denote-filetype-heuristics | +| 39 | denote--rename-file | denote-rename-file-and-buffer | +| 40 | denote--rename-file-prompt | denote-rename-file-prompt | + +If you are writing code that extends Denote and feel that something is +either missing or has remained private, please contact us on the +mailing list, the GitHub/GitLab mirror, or send me an email directly. +I always respond in a timely fashion. + +** Open to everyone +:PROPERTIES: +:CUSTOM_ID: h:27a391cf-8d5e-4d19-942f-46fc52dea80c +:END: + +The most common feedback I get about Denote is that its documentation +is good. As you can tell from these change logs, the plan is to +continue on this path. + +Please note that the communication channels for Denote (mailing list, +mirrors, my personal email) are open to users of all levels. Do not +hesitate to contact us/me. + +Thanks again to everyone for their contributions, direct or indirect, +either in the form of code or the discussion of ideas. Quoting from +the "Acknowledgements" section of the manual (all my packages have +such a section): + +#+begin_quote +Denote is meant to be a collective effort. Every bit of help matters. + ++ Author/maintainer :: Protesilaos Stavrou. + ++ Contributions to code or the manual :: Abin Simon, Alan Schmitt, + Benjamin Kästner, Clemens Radermacher, Colin McLear, Damien Cassou, + Eshel Yaron, Hilde Rhyne, Jack Baty, Jean-Philippe Gagné Guay, Jürgen + Hötzel, Kaushal Modi, Kyle Meyer, Marc Fargas, Peter Prevos, Philip + Kaludercic, Quiliro Ordóñez, Stefan Monnier. + ++ Ideas and/or user feedback :: Abin Simon, Alan Schmitt, Alfredo + Borrás, Benjamin Kästner, Colin McLear, Damien Cassou, Elias Storms, + Frank Ehmsen, Hanspeter Gisler, Jack Baty, Juanjo Presa, Kaushal + Modi, M. Hadi Timachi, Paul van Gelder, Peter Prevos, Shreyas + Ragavan, Summer Emacs, Sven Seebeck, Taoufik, Yi Liu, Ypot, atanasj, + hpgisler, pRot0ta1p, sienic, sundar bp. + +Special thanks to Peter Povinec who helped refine the file-naming +scheme, which is the cornerstone of this project. + +Special thanks to Jean-Philippe Gagné Guay for the numerous +contributions to the code base. +#+end_quote + +* Version 0.6.0 on 2022-08-31 +:PROPERTIES: +:CUSTOM_ID: h:50aba79a-d702-42b4-a2a5-7fa29033f904 +:END: + +Denote is in a stable state. I consider it feature-complete, without +prejudice to possible refinements to its existing feature set. The next +version shall be =1.0.0=. + +** User-facing changes +:PROPERTIES: +:CUSTOM_ID: h:566a770b-399e-47a6-9aa4-326fd6ade9a7 +:END: + ++ The Denote linking facility can now link to any file that has the + Denote file-naming scheme. Before, we limited this feature to what we + consider "note" files, else the supported plain text formats (per + ~denote-file-type~). Thanks to Peter Prevos for the discussion on the + mailing list: . + ++ Date prompts may optionally use the familiar Org date-selection + mechanism that leverages the calendar. This feature is subject to the + user option ~denote-date-prompt-use-org-read-date~. A date prompt is + used by the ~denote-date~ command or, optionally, by the ~denote~ + command when the user option ~denote-prompts~ is configured + accordingly. The manual elaborates on the specificities. Thanks to + Jean-Philippe Gagné Guay for the contribution in pull request 97 at + the GitHub mirror: . + ++ Leading empty spaces at the ~denote~ =TITLE= prompt no longer produce + hyphens: they are simply ignored to keep file names consistent. + Thanks to Peter Prevos for the contribution in pull request 99 at the + GitHub mirror: . + + [ Peter has started the process for copyright assignment to the Free + Software Foundation, though the total contributions are still within + the permitted boundaries. ] + ++ When linking to files that have no front matter, the link's anchor + text (the human-readable part) is derived from the file name =TITLE= + component. We apply a de-hyphenation and capitalisation of its + constituent words. This is not always perfect, but it is better than + something like =this-is-the-title=. Thanks to Peter Prevos for the + original idea in pull request 93 at the GitHub mirror: + . + ++ The active region is now used as the default value of the ~denote~ + command =TITLE= prompt. The idea behind this Do-What-I-Mean-flavoured + patch is to be able to take a note about a subject that appears in a + buffer by simply marking it before invoking the ~denote~ command. + + Thanks to Eshel Yaron for the patch: . + It is below the ~15 line threshold that thus requires no copyright + assignment to the Free Software Foundation. + ++ The ~denote-rename-file-using-front-matter~ command now offers to save + the buffer if appropriate. In the past, it would simply produce an + error asking the user to save the buffer. Thanks to Peter Prevos for + the contribution in pull request 103 at he GitHub mirror: + . + ++ Fixed the text of the confirmation prompt in the command + ~denote-migrate-old-markdown-yaml-tags~. Thanks to Abin Simon for the + patch: . + + This patchset also fixes (i) how a tag is identified for the purposes + of migrating old to new front matter, (ii) the regular expression for + Org front matter keywords + + [ The total changes are below the ~15 line threshold and thus do not + require copyright assignment to the Free Software Foundation. ] + ++ Fixed a bug that prevented the creation of new notes. Thanks to + Juergen Hoetzel for the contribution in pull request 84 at the GitHub + mirror: . This was + done immediately after the release of version =0.5.0= on 2022-08-10 + and was provided to users as version =0.5.1= + + [ The change is below the ~15 line threshold. ] + +** Internal refinements +:PROPERTIES: +:CUSTOM_ID: h:9374b533-faaa-4ab4-b668-f74b5eae7ab5 +:END: + +These make the code simpler and more predictable. As the individual +changes are not user-facing, I invite interested parties to consult the +Git log. Special thanks to Jean-Philippe Gagné Guay for the multiple +contributions (and relevant discussions) over at the GitHub mirror: + +- +- +- +- +- +- + +[ Jean-Philippe has assigned copyright to the Free Software Foundation. + It is required for non-trivial changes. ] + +** For advanced users +:PROPERTIES: +:CUSTOM_ID: h:c6fc05a2-ff31-4a0c-91a1-f64d2cfd6a16 +:END: + +The variable ~denote-file-types~ is an alist of plists which +substantiates the supported file types (per the user option +~denote-file-type~). Properties pertain to the formatting of front +matter and the retrieval of relevant values. The doc string of +~denote-file-types~ explains the details, while the default value uses +the ancillary functions we define. Thanks to Jean-Philippe Gagné Guay +for the relevant contributions in pull request 89 at the GitHub mirror: +. + + +* Version 0.5.0 on 2022-08-10 +:PROPERTIES: +:CUSTOM_ID: h:80b9daaa-c3c8-4457-b109-966bb6a99832 +:END: + +The general theme of this release is to refine what we already offer. +As I explained in some discussions, Denote is feature-complete. We can +always improve the code or add some ancillary function/command/variable, +though all the main ideas have already been implemented. Additional +functionality can be provided by other packages: I remain at the +disposal of anyone willing to write such a package. + +The present release covers more than 150 commits since version 0.4.0 on +2022-07-25. + +All release notes: . + +** Templates for new notes +:PROPERTIES: +:CUSTOM_ID: h:0878125f-8392-48e6-aeff-1469eb1e18fc +:END: + +We now provide the ~denote-templates~ user option. A "template" is +arbitrary text that Denote will add to a newly created note right below +the front matter. + +Templates are expressed as a =(KEY . STRING)= association. + +- The =KEY= is the name which identifies the template. It is an + arbitrary symbol, such as =report=, =memo=, =statement=. + +- The =STRING= is ordinary text that Denote will insert as-is. It can + contain newline characters to add spacing. The manual of Denote + contains examples on how to use the ~concat~ function, beside writing + a generic string: + . + +The user can choose a template either by invoking the new command +~denote-template~ or by changing the user option ~denote-prompts~ to +always prompt for a template when calling the ~denote~ command. + +Thanks to Jean-Philippe Gagné Guay for refinements to this facility. +Done in pull request 77 on the GitHub mirror: +. + +[ Jean-Philippe has assigned copyright to the Free Software Foundation. ] + +** Revised format for Org =#+filetags= entry +:PROPERTIES: +:CUSTOM_ID: h:17688b79-cb1b-4a59-831e-fbf2a81245d3 +:END: + +Denote used to format tags in Org files by separating them with two +spaces: + +#+begin_example +#+filetags: tag1 tag2 +#+end_example + +While this worked for some obvious use-cases, it is not supported by +Org. The Org documentation stipulates that tags be separated by the +colon sign. The above would then be written thus: + +#+begin_example +#+filetags: :tag1:tag2: +#+end_example + +Denote now conforms with Org's specifications. To help users update +their existing notes, we provide the ~denote-migrate-old-org-filetags~ +command. It will perform the conversion in all Org files that had the +old notation. As with all Denote operations that rewrite file contents, +it DOES NOT SAVE BUFFERS. The user is expected to review the changes, +such as by using ~diff-buffer-with-file~. Multiple buffers can be saved +with ~save-some-buffers~ (check its doc string). + +This command is provided for the convenience of the user. It shall be +deprecated and eventually removed from future versions of Denote. + +If you need help with any of this, please do not hesitate to contact me +either in private or in one of Denote's official channels (mailing list, +GitHub/GitLab mirror). + +Thanks to Alan Schmitt for bringing this matter to my attention: +. +Also thanks to Jean-Philippe Gagné Guay for commenting on it as it +helped me decide to include the command in =denote.el=: +. + +** Revised format for Markdown+YAML =tags:= entry +:PROPERTIES: +:CUSTOM_ID: h:205a09cf-0159-425e-a6b3-41700fa3ad31 +:END: + +This is the same idea as above. Before, we were making the mistake of +using incorrect YAML notation: + +#+begin_src yaml +tags: tag1 tag2 +#+end_src + +Now we do: + +#+begin_src yaml +tags: ["tag1", "tag2"] +#+end_src + +This is how the TOML variant always worked. + +For the user's convenience, we provide a command to migrate from the old +to the new syntax: ~denote-migrate-old-markdown-yaml-tags~. + +** Changes to file renaming and front matter rewriting +:PROPERTIES: +:CUSTOM_ID: h:15ecb4e8-d1ce-4e42-b74d-a3a046d93220 +:END: + +Denote adds "front matter" to newly created notes which includes data +such as the title and keywords/tags of the document. Strictly speaking, +the front matter is not required by Denote. It is provided for the +user's convenience, such as for readability or if they want to use the +note with other programs (e.g. Org export, a blog with Hugo/Jekyll, +...). + +Denote provides commands which help the user rename their notes, by +changing the file name's =TITLE= and/or =KEYWORDS= components (per +Denote's file-naming scheme). These commands also operate on the front +matter to keep the data between file name and file contents in sync +(again, for the user's convenience). + +For this release we have consolidated and refined our offerings in order +to improve their ergonomics. All changes are the result of fruitful +discussions on the mailing list and the issue tracker of the GitHub +mirror: + +- +- +- +- +- + +Thanks to (A-Z) Hanspeter Gisler, Jean-Philippe Gagné Guay, and Peter +Prevos for their participation. + +Also thanks to Jean-Philippe Gagné Guay for relevant code contributions +(please consult the Git log for the minutiae): + +- +- +- +- +- + +*** Renaming a single file +:PROPERTIES: +:CUSTOM_ID: h:1d695e54-1481-42dd-916b-c0542c48aa6f +:END: + +The commands ~denote-dired-rename-file-and-add-front-matter~ and +~denote-dired-rename-file~ are deprecated and superseded by the new +~denote-rename-file~. Please update any key bindings in your setup. + +The difference between the old commands and the new ~denote-rename-file~ +is that the latter will now insert front matter to supported file types +(per ~denote-file-type~) if they have none. This basically means that, +e.g., renaming a generic Org/Markdown/Plain text file with +~denote-rename-file~ will update its file name to comply with Denote's +file-naming scheme and also add the appropriate front matter (it +"converts" it to a Denote note). If front matter exists, this command +will rewrite it to reflect the changes to the file name's =TITLE= and/or +=KEYWORDS=. + +Consult the manual for the details: +. + +Or, if the new version of the GNU ELPA package is installed, evaluate: + +#+begin_src emacs-lisp +(info "(denote) Rename a single file") +#+end_src + +The user option ~denote-dired-rename-expert~ is obsolete. Denote always +asks for confirmation when renaming a single file. This is because the +user can rely on batch-renaming commands which ask for confirmation only +once per batch. + +*** Renaming multiple files at once +:PROPERTIES: +:CUSTOM_ID: h:82455fb4-576b-4753-af66-ac48fd158327 +:END: + +The command ~denote-dired-rename-marked-files-and-add-front-matter~ is +deprecated and its functionality is absorbed by the existing +~denote-dired-rename-marked-files~ command. The deprecated command was +used to insert front matter to supported file types (per +~denote-file-type~) that had none. We now handle this internally, thus +streamlining the experience for the user. + +Refer to the manual for the details: + + +Assuming the latest Info manual is installed, evaluate: + +#+begin_src emacs-lisp +(info "(denote) Rename multiple files at once") +#+end_src + +*** Renaming a single file based on its front matter +:PROPERTIES: +:CUSTOM_ID: h:d913e369-9325-46c4-985b-cf5b3e35372b +:END: + +Introduced the ~denote-rename-file-using-front-matter~ command. This is +new functionality we provide which uses the front matter as input to +perform a rename of the file. The aforementioned offerings prompt for +input via the minibuffer and propagate the changes firstly to the file +name and subsequently to the front matter. Whereas with the command +~denote-rename-file-using-front-matter~, the user can edit the front +matter manually and then invoke the command to pass the changes to the +file name, subject to a confirmation. Relevant entries are the title +and tags/filetags (depending on the file type). The date and the +identifier are not pertinent. Identifiers in file names are NEVER +rewritten by Denote. + +Consult the manual: +. + +With the latest package, evaluate: + +#+begin_src emacs-lisp +(info "(denote) Rename a single file based on its front matter") +#+end_src + +*** Renaming multiple files based on their front matter +:PROPERTIES: +:CUSTOM_ID: h:4efc6c14-fd71-4bd8-8bb1-e8e720b98eff +:END: + +The command ~denote-dired-rename-marked-files-using-front-matter~ +completes the set of features we provide for syncing between file name +and front matter. It applies to all marked files in a Dired buffer. + +Read the manual to understand how the command works and what it does +exactly: . + +Or evaluate: + +#+begin_src emacs-lisp +(info "(denote) Rename multiple files based on their front matter") +#+end_src + +*** Add missing front matter on demand +:PROPERTIES: +:CUSTOM_ID: h:32a103be-71a2-48e4-a18e-7727c04545ed +:END: + +Sometimes the user may have incomplete front matter, perhaps due to a +mistake that was saved on disk. The command ~denote-add-front-matter~ +appends a new front matter block to the current note. + +Read: + + +Or evaluate: + +#+begin_src emacs-lisp +(info "(denote) Regenerate front matter") +#+end_src + +** Faces for Denote links +:PROPERTIES: +:CUSTOM_ID: h:507fb46c-a2e9-48a7-8cd2-53c5fc73394d +:END: + +We provide the ~denote-faces-link~ and the ~denote-faces-broken-link~. +The latter is only relevant for Org, as Emacs' standard button mechanism +does not have a way to apply a face dynamically. + +This is a change for themes/tinkerers who need to differentiate +=denote:= links from other links. Otherwise, the presentation is the +same as before. + +Thanks to Peter Prevos for asking about it on the mailing list: +. + +** Use of XDG path in ~denote-directory~ +:PROPERTIES: +:CUSTOM_ID: h:efa3049e-f1fa-48ff-af7d-d16edc677704 +:END: + +The default value of the ~denote-directory~ user option used to be +=~/Documents/notes= (subject to some conversion via Elisp). Denote now +conforms with the freedesktop.org specifications by using the =XDG= +directory for =DOCUMENTS= instead of =~/Documents=: +. + +Users who already bind the ~denote-directory~ are not affected by this +change. Same for those who do not tinker with =XDG= environment +variables and/or do not use some exotic setup. + +Thanks to Philip Kaludercic for the patch: + + +** Bespoke major-mode for the backlinks' buffer +:PROPERTIES: +:CUSTOM_ID: h:feb9a0ed-ba15-486e-ae11-5b222b00bc31 +:END: + +The backlinks' buffer now uses the ~denote-backlink-mode~ instead of the +generic ~special-mode~. The former derives from the latter. It binds +keys to move between links with =n= (next) and =p= (previous). These +are stored in the ~denote-backlink-mode-map~ (use =M-x describe-mode= +(=C-h m=) in an unfamiliar buffer to learn more about it). + +Thanks to Philip Kaludercic for the patch: + + +** Changes to the manual +:PROPERTIES: +:CUSTOM_ID: h:80217a39-86b8-4310-b7c4-dcc14e0b98fd +:END: + ++ Documented all of the aforementioned. Improved how information is + presented and, generally, iterated on an already comprehensive + document. + ++ Introduced a node which explains how to tweak the front matter: + . + Or evaluate: + + #+begin_src emacs-lisp + (info "(denote) Change the front matter format") + #+end_src + ++ Updated the reference to =consult-notes=. This is a package that uses + the =consult= interface to provide access and search facilities for + notes. It can integrate with Denote. Thanks to Colin McLear for the + change in pull request 70 on the GitHub mirror: + . + + [ The change is below the ~15 line threshold and thus does not require + copyright assignment to the Free Software Foundation. ] + +** Internal restructuring +:PROPERTIES: +:CUSTOM_ID: h:5d09d0af-3c25-4419-8448-90b8e1adab0d +:END: + ++ All Denote code is consolidated in =denote.el=. We no longer maintain + separate files like =denote-link.el=, =denote-dired.el=, etc. Users + who had ~require~ calls to such libraries must remove them and only + keep: + + #+begin_src emacs-lisp + (require 'denote) + #+end_src + ++ User options that have an entry in the manual will now provide a link + to it via their Help buffer and/or the Custom UI. This is done by + adding the =:link= attribute to their declaration. + + Furthermore, user options and faces now specify the version of Denote + that last affected their value (e.g. ~denote-directory~, which was + mentioned above for the XDG spec, now informs the user that it changed + for version =0.5.0=). + + [ I learnt these by developing the =modus-themes=. ] + ++ The variables ~denote-last-title~, ~denote-last-keywords~, + ~denote-last-buffer~, and ~denote-last-front-matter~ are all obsolete. + These were used prior to version =0.1.0= to help with development but + are now deemed surplus to requirements. + ++ Lots of changes were made to private functions, variables, doc + strings, and comments, in the interest of simplifying the code and/or + ensuring consistency in how operations are carried out. Though + everything is the same for the end-user. + +Thanks to Jean-Philippe Gagné Guay for the numerous contributions on the +GitHub mirror. They are important for Denote, though the user does not +need to know what is happening internally (consult the Git log for the +details): + +- +- +- +- +- +- +- +- + +** Discussions +:PROPERTIES: +:CUSTOM_ID: h:79089c06-9e0c-49cc-9d53-a1a2fd72fb65 +:END: + +*** Encrypting Denote notes +:PROPERTIES: +:CUSTOM_ID: h:87e4556a-4864-4955-a98c-62b2e6a509c3 +:END: + +Paul van Gelder asked about this on the mailing list. I provided +guidelines on what can be done, though did not record anything in the +manual: I prefer to elicit more feedback from users. The gist is that +Emacs already has all the requisite functionality, though encryption per +se is outside the scope of Denote: +. + +Denote's relevant internal mechanisms will recognise files ending in +=.gpg= (e.g. for fontification in Dired). + +*** Visualise usage of Denote keywords +:PROPERTIES: +:CUSTOM_ID: h:d94ee5e3-0a54-404c-b44b-34edc3703fbc +:END: + +Peter Prevos shared a proof-of-concept way to visualise keywords in the +~denote-directory~ and show usage statistics: +. + +We do not include this information in the manual, as we wait for the +fully fledged code. Though do give it a try if you are interested and, +perhaps, share your thoughts for Peter's consideration. + +*** Conflict between ~denote-dired-mode~ and ~diredfl-mode~ +:PROPERTIES: +:CUSTOM_ID: h:0cbf504c-676c-436e-8ae8-e7115368e691 +:END: + +Hilde Rhyne shared a workaround they have to disable ~diredfl-mode~ in +the buffers where ~denote-dired-mode~ is enabled. The conflict between +the two is a known issue that is acknowledged in the manual: +. + +I think we need a proper solution in the code we provide, so this +workaround is not mentioned in the manual. + +*** Why doesn't Denote provide a search facility? +:PROPERTIES: +:CUSTOM_ID: h:068108f4-a4fa-4ff8-be49-f1f10a862451 +:END: + +There was a discussion started by Fourchaux, with the participation of +basaran and Andre0991 on the GitHub mirror: +. + +The gist of my answer is that Denote does not need to provide such a +facility because notes are ordinary files: whatever the user already has +for them should apply to Denote. If the user has nothing to search +through files, they anyhow need something that works outside the +confines of Denote: a =denote-SEARCH= command is not an adequate +solution. + +Emacs has numerous built-in commands, such as ~grep~ (~lgrep~ and +~rgrep~), ~project-find-regexp~, ~find-grep-dired~, ~ibuffer-do-occur~, +... Furthermore, there are lots of high quality packages that have +their own wrappers or extensions for searching file contents, such as +the =ivy= and =helm= completion frameworks, as well as =consult= (the +commands ~consult-grep~ and ~consult-ripgrep~), =consult-notes=, =rg=, +=deadgrep=, =deft=, and probably plenty more that do not come to mind +right now. + +I strongly encourage the user to find a universal search solution to the +problem of searching file contents. + +* Version 0.4.0 on 2022-07-25 +:PROPERTIES: +:CUSTOM_ID: h:1c8098ee-089c-4511-bc6a-4140aab01321 +:END: + ++ Defined the ~denote-link-dired-marked-notes~ command. It lets the + user produce a typographic list of links to the note files that are + marked in Dired. The list is written at point. If there are multiple + buffers which visit Denote notes, the command first prompts with + minibuffer completion for one among them. + + In terms of workflow, ~denote-link-dired-marked-notes~ complements the + ~denote-link-add-links~ command for those cases where it is easier to + select files than write an elegant regular expression. + ++ Implemented the ~denote-dired-rename-marked-files~ command. This + provides a much-requested facility to perform the familiar renaming + operation on a set of files. In particular: + + - the file's existing file name is retained and becomes the =TITLE= + field, per Denote's file-naming scheme; + + - the =TITLE= is sluggified and downcased, per our conventions; + + - an identifier is prepended to the =TITLE=; + + - the file's extension is retained; + + - a prompt is asked once for the =KEYWORDS= field and the input is + applied to all file names; + + - if the file is recognised as a Denote note, the command rewrites its + front matter to include the new keywords. A confirmation to carry + out this step is performed once at the outset. Note that the + affected buffers are not saved. The user can thus check them to + confirm that the new front matter does not cause any problems + (e.g. with the command ~diff-buffer-with-file~). Multiple buffers + can be saved with ~save-some-buffers~ (read its doc string). + + Parts of ~denote-dired-rename-marked-files~ were added or refined over + a series of commits. Consult the Git log for the minutia. Thanks to + Jean-Philippe Gagné Guay for the relevant additions in pull requests + 51 and 52 on the GitHub mirror: + + - + - + + Jean-Philippe has assigned copyright to the Free Software Foundation. + ++ Improved how the ~denote-dired-rename-file~ command rewrites front + matter. Before, it would perform a replacement of the whole block, + which had the adverse effect of overwriting custom front matter + entries. Now, it only targets the lines which hold the title and + keywords, leaving everything else intact. Thanks to Peter Prevos for + reporting the problem and testing the solution to it in issue 60 on + the GitHub mirror: . + ++ Introduced the ~denote-dired-rename-file-and-add-front-matter~ command + that always prepends front matter to a file whose extension is among + the supported ones (per the user option ~denote-file-type~). This + differs from the standard ~denote-dired-rename-file~ command which + only rewrites the front matter's title and keywords if they exist. + + In practice, ~denote-dired-rename-file-and-add-front-matter~ empowers + the user to convert a generic text file to a Denote note. + + This command was originally added by Jean-Philippe Gagné Guay in pull + request 49 on the GitHub mirror and refined in subsequent commits: + . Also read issue 48 + where this idea was originally discussed: + . + ++ Added the ~denote-dired-rename-marked-files-and-add-front-matters~ + command, which is like the ~denote-dired-rename-marked-files~ but adds + front matter instead of rewriting existing one, just how the command + ~denote-dired-rename-file-and-add-front-matter~ does it (both are + mentioned above). Thanks to Jean-Philippe Gagné Guay for the + refinements to it in pull request 53 on the GitHub mirror: + . + ++ Wrote an interactive spec for ~denote-link-buttonize-buffer~. It can + now be invoked with =M-x= or a key binding, should the need arise. + This function is normally called via a hook and takes effect in plain + text as well as Markdown files. + ++ Extended the fontification rules so that file names with non-ASCII + characters are styled properly. This issue was brought up on the + mailing list by Frank Ehmsen and was discussed with the participation + of Peter Prevos: + . + + The same topic was raised at the same time on the GitHub mirror by + user hpgisler in issue 61: + . + + After some discussion, we agreed on the right approach, which was + formalised by Peter Prevos as pull request 64 on the GitHub mirror: + . The change is below + the ~15 line threshold and thus does not require copyright assignment + to the Free Software Foundation. + ++ Made the registration of the =denote:= custom Org hyperlink type + conditional on the availability of the ~org~ feature. In other words, + those who do not use Org will not be loading this part of the code. + Thanks to Abin Simon for reporting the problem and for showing how + Elfeed handles this case. This was done in issue 47 on the GitHub + mirror: . + ++ Ensured that duplicate keywords are not produced by the relevant + prompt. Thanks to user Taoufik for the contribution in pull request + 50 on the GitHub mirror: . + The change is below the ~15 line threshold and thus does not require + copyright assignment to the Free Software Foundation. + ++ Fixed a typo in the reference to the ~crm-separator~ in the manual. + David Wilson (System Crafters channel) spotted the error in a recent + live stream whose main topic was about Denote (thanks, by the way!): + . + ++ Addressed an inconsistency in the command ~denote-link-find-file~ + where it would not recognise links without a title in their format + (those can be inserted by passing a prefix argument (=C-u= by default) + to the commands that insert links, such as ~denote-link~). + ++ Attached conditionality to the ~denote~ command's =SUBDIRECTORY= + argument, so that it does not create new file paths. This is only + relevant for those who call ~denote~ from Lisp. Interactive use is + the same as before. + ++ Clarified that the user option ~denote-org-capture-specifiers~ can + accept arbitrary text in addition to the formatting specifiers that + Org's capture mechanism introduces. + ++ Explained in the manual why ~denote-org-capture-specifiers~ is needed + instead of writing the capture template directly the way one normally + does. The gist is that because our file names are derived dynamically + based on user input, we need to account for the sequence in which the + value of arguments is reified by ~org-capture~. + ++ Refactored how notes are prepared internally. Thanks to Jean-Philippe + Gagné Guay for the contribution in pull request 55 on the GitHub + mirror: . + ++ Declared the ~denote-punctuation-excluded-extra-regexp~ variable which + is, for the time being, targeted at experienced users. Its purpose is + to extend what we consider "illegal" punctuation for the file name. + Thanks to pRot0ta1p for the feedback in issue 57 over at the GitHub + mirror: . Example + based on the input of pRot0ta1p: + + #+begin_src emacs-lisp + (setq denote-punctuation-excluded-extra-regexp + "[『』〖〗{}「」【】〔〕[]()《》〈〉«»!#¥%…&"'*,。;:、?—]*") + #+end_src + + The ideal is to make ~denote--punctuation-regexp~ work for all + scripts, but that may be unrealistic. + ++ Clarified what the manual means by "attachments" to notes. Those are + for Org, if the user resorts to the relevant Org mechanisms. Denote + does not do any of that. + ++ Revised the parsing of a date input as used in the ~denote-date~ + command or related. The idea is to turn =2020-01-15= into something + like =2020-01-15 16:19= by using the current time, so that the hour + and minute component is not left to =00:00= when the user does not + specify it explicitly. + + This reduces the burden on the user who would otherwise need to input + the time value in order to avoid the error of duplicate identifiers in + the scenario where the same date is used more than once. + + The change also addresses a difference between Emacs 28 and Emacs 29 + where the former does not read dates without a time component. + + Thanks to Peter Prevos for the feedback in issue 58 over at the GitHub + mirror: . + ++ Fixed compilation warnings in Emacs 29 about the format of doc strings + that need to output a literal single quote. Thanks to Kyle Meyer for + the patch, which was sent on the mailing list: + . + ++ Fixed typo in the user option ~denote-prompts~ about the + ~crm-separator~. Thanks to Kyle Meyer for the patch, which was sent + on the mailing list: + . + ++ Made the built-in =subr-x= library a runtime dependency, due to + complications with the ~when-let*~ form. The problem was made + manifest in a renaming operation, though it was not about renaming per + se. Thanks to hpgisler for reporting the problem in issue 62 and for + testing the proposed solution: + . + ++ Streamlined the use of the =seq= library instead of =cl-lib=, as we + were already using the former more heavily and there was no need for + the latter. Thanks to Philip Kaludercic for pointing this out on the + emacs-devel mailing list: + . + ++ Added a generic =README.md= file to placate the Git forges. Neither + SourceHut nor GitHub/GitLab are fully compliant with the Org markup we + use in =README.org= (we use Org because it is easy to generate the + Info manual and HTML pages out of it). SourceHut will not render the + file at all, while the others render it but do not parse it properly. + ++ Made several other internal tweaks and refinements in the interest of + robustness and/or clarity. + ++ Rewrote all relevant documentation. + +** Non-changes +:PROPERTIES: +:CUSTOM_ID: h:0ac79968-a575-4380-addc-d58cc2b5f627 +:END: + +The following are not part of any changes that were made during this +release cycle, though they provide potentially interesting insight into +the workings of the project. + ++ Identifiers with milliseconds :: Denote's identifier format extends up + to seconds. This is the product of years of experimentation and is, + in my opinion, the best compromise between usability/readability and + precision. If a user produces two notes within a fraction of a + second, then yes they will have duplicate identifiers. In principle, + there is no reason not to address this potential problem, provided we + do not compromise on Denote's file-naming scheme (making the + identifier less readable is a compromise). We shall see what the best + course of action is. Thanks to Felipe Balbi and Jean-Philippe Gagné + Guay for the discussion thus far in issue 54 on the GitHub mirror: + . + ++ Denote and evil-mode :: Users of evil-mode do not have to worry about + Denote, as we do not define any key bindings. The manual includes + sample configuration, which proposes some key bindings, but that is + the user's prerogative. Thanks to Saša Janiška and Alan Schmitt for + their participation on the mailing list: + . + ++ Denote and Citar :: Peter Prevos started developing a package that + connects Denote with Citar: . + The idea is to use notes as part of one's bibliography. Discussions + which include sample code on how to leverage ~denote~ from Lisp: + + - + - + - + ++ Denote and graph of connections :: Saša Janiška asked whether Denote + will provide some way to visualise links between notes. The answer is + negative. Denote's scope is clearly delineated and its feature set is + largely complete (notwithstanding refinements to what we already + provide). Peter Prevos is experimenting with some code that uses the + R language. Any such facility will have to be implemented as a + separate package. I remain at the disposal of anyone who needs help + with Denote's internals. Thanks to the aforementioned fellows for + their participation on the mailing list: + . + ++ Denote's scalability :: There was a discussion whether Denote will + work well with very large sets of files. The short answer is that it + will work the same way Emacs and/or standard Unix tools do: good + enough! If there are improvements to be made, which do not jeopardise + the principles of the project, we shall implement them without + hesitation. Thanks to Saša Janiška and Peter Prevos for their + participation on the mailing list: + . + ++ Denote's minimum requirement of Emacs 27.2 :: We cannot depend on + Emacs 27.1 due to this message from the byte compiler: + + : You should depend on (emacs "27.2") or the (org "9.3") package if you need `org-link-open-as-file'. + + Depending on Org is not an option because Denote optionally works + without Org, so Emacs 27.2 is what we have to opt for. If your + operating system does not provide this version in package format, + please petition its maintainers/providers to do so. Thanks to + Alexander for asking about it on the mailing list: + . + +Finally, a mildly interesting piece of trivia: we have exceeded 600 +commits since the first day of the project's Git history on 2022-06-04 +(the actual history is much longer). That averages to more than 10 per +day! I think things will slow down eventually. + +* Version 0.3.0 on 2022-07-11 +:PROPERTIES: +:CUSTOM_ID: h:6864cfd4-d0be-4c89-b313-39ba6e892a03 +:END: + ++ Fixed how references are analysed to produce the backlinks' buffer. + This should resolve the issue that some users faced where the + backlinks would not be produced. + + The previous implementation would not yield the appropriate results if + (i) the value of the user option ~denote-directory~ was a "project" + per the built-in project.el and (ii) the link to the given entry was + from a subdirectory. In short, the references were sometimes returned + as relative file paths, whereas they should always be absolute. + Thanks to Jean-Philippe Gagné Guay for the feedback in issue 42 over + at the GitHub mirror: . + + [ Jean-Philippe has assigned copyright to the Free Software + Foundation. It is a prerequisite for contributing to core Emacs + and/or any package distributed via the official GNU ELPA. ] + ++ Addressed a regression in the function ~denote-directory~ (this is the + function that normalises the variable of the same name) which + prevented it from returning an expanded file path. This too + contributed to problems with the backlinking facility. Thanks to + Jean-Philippe Gagné Guay for the contribution in pull request 44 over + at the GitHub mirror: . + + Also thanks to user pRot0ta1p for the relevant feedback in issue 43 + (also on the mirror): . + More thanks to Alfredo Borrás, Benjamin Kästner, and Sven Seebeck for + their comments in a related thread on the mailing list: + . + These discussions showed we had a problem, which we managed to + identify. + ++ Introduced the user option ~denote-prompts~ (read its doc string or + the relevant entry in the manual). It governs how the standard + ~denote~ command for creating new notes will behave in interactive + usage. By default, ~denote~ prompts for a title and keywords. With + ~denote-prompts~, the command can also ask for a file type (per + ~denote-file-type~), subdirectory of the ~denote-directory~, and a + specific date+time. Prompts occur in the order they are specified. + Furthermore, the ~denote-prompts~ can be set to values which do not + include the title and keywords. This means that the resulting file + names can be any of those permutations: + + : DATE.EXT + : DATE--TITLE.EXT + : DATE__KEYWORDS.EXT + + Recall that Denote's standard file-naming scheme is defined as follows + (read the manual for the details): + + : DATE--TITLE__KEYWORDS.EXT + + For our purposes, Denote will work perfectly fine for linking and + backlinking, even if file names do not include the =TITLE= and + =KEYWORDS= fields. However, the user is advised to consider the + implications on usability: notes without a descriptive title and/or + useful keywords may be hard to filter and practically impossible to + manage at scale. File names without such information should at least + be added to subdirectories which themselves have a descriptive name. + + At any rate, Denote does not have strong opinions about one's + workflow. The standard file name is the culmination of years of + experience. + + Consider the ~denote-prompts~ the affirmative answer to the question + "Can keywords be optional?" as posed by Jack Baty on the mailing list: + . + + Thanks to Jean-Philippe Gagné Guay for the original contribution in + commit 9b981a2. It was originally part of a pull request, but due to + some internal changes I had to merge it as a patch and technically the + web UI did not count the PR as "merged" (though it was in terms of + substance). + ++ Refactored the ~denote~ command to (i) accommodate the new user option + ~denote-prompts~ via its interactive specification and (ii) be more + flexible when called from Lisp. The latter scenario is for advanced + users or, generally, those who can maintain some custom code in their + configuration. A case in point is one of the examples we show in the + manual for a programmatic way to create notes that automatically get + the =journal= tag: + + #+begin_src emacs-lisp + (defun my-denote-journal () + "Create an entry tagged 'journal', while prompting for a title." + (interactive) + (denote + (denote--title-prompt) + '("journal"))) + #+end_src + + Notice that the ='("journal")= is a list of strings even for a single + keyword. Whereas before a single one was a plain string. This is a + breaking change. + + Please consult the doc string of the ~denote~ command for the + technicalities. + ++ Refashioned the interactive convenience functions of ~denote-type~, + ~denote-date~, ~denote-subdirectory~ to leverage the ~denote-prompts~ + user option while calling ~denote~ interactively. In practical terms, + they no longer accept any arguments when called from Lisp. Users who + need a programmatic approach are advised to either call ~denote~ + directly, or check how these commands ~let~ bind the ~denote-prompts~ + to carry out their operations. The doc string of each command + explains how it works. Or evaluate this to check the manual: + + #+begin_src emacs-lisp + (info "(denote) Convenience commands for note creation") + #+end_src + + Else visit: + + ++ Documented how the user option ~denote-directory~ can accept a local + value. This is pertinent to scenaria where the user needs to maintain + separate directories of notes. By "separate" we mean sets of notes + that do not communicate with each other, cannot create links between + them, etc. The manual delves into the technicalities. If you have + the Info entry installed, evaluate: + + #+begin_src emacs-lisp + (info "(denote) Maintain separate directories for notes") + #+end_src + + Else visit: + . + + Thanks to user "Summer Emacs" for starting the discussion on the + mailing list, and Benjamin Kästner for their participation: + . + ++ Added an entry to the manual's Frequently Asked Questions about a + failed search for backlinks. It includes sample code that users of + Windows can apply, if necessary. (The error is not Denote's fault.) + Thanks to Benjamin Kästner for the patch, which is below the ~15 line + threshold and thus does not require copyright assignment to the Free + Software Foundation: + . + ++ Removed duplicate entries from the list of file paths that the =xref= + library returns for the purposes of backlinking. Thanks to + Jean-Philippe Gagné Guay for the contribution in pull request 44 on + the GitHub mirror: . + ++ Applied an appropriate face to the backlinks' button to mitigate an + error. Thanks to Jean-Philippe Gagné Guay for the contribution in + pull request 45 on the GitHub mirror and for later testing a + subsequent tweak: . + ++ Simplfied all the faces we define to make them work with all themes. + The previous colours were consistent with the =modus-themes=: + . + ++ Refined how strings are sluggified under all circumstances. Before, a + nil value for the user option ~denote-allow-multi-word-keywords~ would + have the adverse effect of joining all the strings in the title field + of the file name. The intent always was to do that only for + multi-word keywords, not the title. This change was part of a hotfix, + formalised as version =0.2.1= a day after the release of =0.2.0=. + ++ Made the fontification rules more robust, while avoiding any false + positives. This was done over a series of commits as it had + implications for the file name permutations that were mentioned + earlier. Thanks to Jean-Philippe Gagné Guay for the patches and/or + discussion about the merits of each change and concomitant + considerations: + + - https://github.com/protesilaos/denote/pull/36 + - https://github.com/protesilaos/denote/pull/38 + - https://github.com/protesilaos/denote/pull/40 + - https://github.com/protesilaos/denote/pull/42 + ++ Rewrote all relevant entries in the manual to reflect all the + user-facing aspects of the aforementioned. + ++ Discussed a use-case of rewriting old journal entries as Denote-style + files. As of this writing, we do not support migration of files in + bulk. It might happen at some point, though it is no mean task. + Thanks to Summer Emacs and Alan Schmitt for their participation: + . + + An aside here as this topic was brought up: my packages are open to + users of all skill levels and is why I maintain a mailing list as well + as mirrors of the official git repository on SourceHut. Do not + hesitate to ask a question. If, for whatever reason, those + communication channels are not appropriate, you are welcome to contact + me in private: . + +Thanks again to Jean-Philippe Gagné Guay for the numerous contributions. +Please read the commit log for the minutia, as this change log entry +omitted some of the finer yet important details. + +* Version 0.2.0 on 2022-07-04 +:PROPERTIES: +:CUSTOM_ID: h:2002fee6-3f0c-48be-9727-6d4e20f34856 +:END: + ++ Version =0.1.0= (from 2022-06-27) was never built as a package. The + reason is that the GNU ELPA machinery reads the =Version:= header of + the main file, not the git tag. As the original commit in =denote.el= + included =Version: 0.1.0=, GNU ELPA rightly tries to build the package + using that reference. But because at that time I had not yet updated + the Copyright header to name the Free Software Foundation, the package + could not be prepared. As such, please consider this release to be + the "first formal stable version". My apologies for the delay, + contrary to what was promised in the last change log entry. + + - Prospective users are advised to read the manual: + . For a video demonstration: + . + + - Thanks to Benjamin Kästner for reporting the issue with the GNU ELPA + package on the mailing list: + . + ++ Originally, Denote was designed to only work with notes in a flat + directory. With code contributions from Jean-Philippe Gagné Guay, + support for subdirectories of the user option ~denote-directory~ is + now available. This covers the case of creating links between notes, + following them, and viewing the backlinks' buffer of the current + entry. + + - Thanks to Jean-Philippe for the contributions which took place on + the GitHub mirror: + + + + + + + + + - Jean-Philippe Gagné Guay has assigned copyright to the Free Software + Foundation. This is a prerequisite to contribute code to any + package on the official GNU ELPA archive (and to emacs.git for that + matter). + ++ The new ~denote-subdirectory~ command lets the user select a directory + to place the new note in. Available candidates are the value of the + ~denote-directory~ as well as all of its subdirectories, minus =.git=. + In future versions, we will consider how to provide a blocklist or a + regexp filter for the user to specify which subdirectories should be + omitted from minibuffer completion. Please consider providing your + feedback on the technicalities. + + - Thanks to Jean-Philippe Gagné Guay and Shreyas Ragavan for the + feedback in issue 31 on the GitHub mirror: + . + + - Thanks to Jean-Philippe Gagné Guay for fixing a potential problem in + how directories are represented when commands enter the directory + instead of selecting it (again, at the GitHub mirror): + . + ++ From 2022-06-24 to 2022-07-03, Denote provided support for links + between Org notes that leveraged the =id:= hyperlink type. + Discussions on the mailing list and the GitHub mirror revealed the + longer-term problems in our implementation. In the Annex below, I + provide my detailed opinion on the matter. The gist is that Denote + does not---and will not---create =id:= links between its notes, but + shall use the =denote:= hyperlink type instead (which works like the + standard =file:= type). As the Annex explains, Denote is not org-roam + lite and we try not to engender such false expectations. + + - Despite the fact that the relevant patches are no longer applicable, + I wish to thank Kaushal Modi and Jean-Philippe Gagné Guay for their + contributions over at the GitHub mirror: + + + + + + ++ The user option ~denote-date-format~ controls how the date and time is + recorded in the file's contents (what we call "front matter"). When + nil (the default value), we use a file-type-specific format (also + check the user option ~denote-file-type~): + + - For Org, an inactive timestamp is used, such as =[2022-06-30 Wed 15:31]=. + + - For Markdown, the RFC3339 standard is applied: =2022-06-30T15:48:00+03:00=. + + - For plain text, the format is that of ISO 8601: =2022-06-30=. + + If the value is a string, ignore the above and use it instead. The + string must include format specifiers for the date. These are described + in the doc string of ~format-time-string~. + + The ~denote-date-format~ supersedes the now obsolete + ~denote-front-matter-date-format~. + + Thanks to Peter Prevos and Kaushal Modi for their feedback in issue 27 + on the GitHub mirror: . + ++ All the faces we define are now declared in the =denote-faces.el= + file. The fontification rules are shared by ~denote-dired-mode~ and + the backlinks' buffer (invoked by ~denote-link-backlinks~ and + controlled by the user option ~denote-link-fontify-backlinks~). The + current list of faces: + + - ~denote-faces-date~ + - ~denote-faces-delimiter~ + - ~denote-faces-extension~ + - ~denote-faces-keywords~ + - ~denote-faces-subdirectory~ + - ~denote-faces-time~ + - ~denote-faces-title~ + ++ Named the mailing list address as the =Maintainer:= of Denote. + Together with the other package headers, they help the user find our + primary sources and/or communication channels. This change conforms + with work being done upstream in package.el by Philip Kaludercic. I + was informed about it here: + . + ++ Fixed how keywords are inferred and combined. The previous code did not + work properly when the user option =denote-infer-keywords= was nil. + It would return a list of symbols, with the parentheses, whereas the + file name needs a string where each keyword is delimited by an + underscore. + ++ Simplified how information in the front matter is retrieved. It fixes + cases where, for example, a special character at the end of the title + was ignored. Thanks to Jean-Philippe Gagné Guay for the patch over at + the GitHub mirror: . + ++ Rewrote parts of the manual in the interest of clarity. + +** Annex about discontinuing support for org-id +:PROPERTIES: +:CUSTOM_ID: h:647d6155-1ac3-4ecb-bd4c-06d09fecd3ba +:END: + +My thanks for their participation in the discussions go to Jean-Philippe +Gagné Guay, Kaushal Modi, and Shreyas Ragavan. + +#+begin_example +commit f35ef05cb451f265213c3aafc1e62c425b1ff043 +Author: Protesilaos Stavrou +Date: Sun Jul 3 17:34:38 2022 +0300 + + REMOVE support for 'id:' hyperlink types + + The original idea was to support the 'org-id' library on the premise + that it makes Denote a good Emacs citizen. However, discussions on the + mailing list[0] and the GitHub mirror[1] have made it clear to me that + 'org-id' is not consistent with Denote's emphasis on simplicity. + + To support the way 'org-id' works, we will eventually have to develop + some caching mechanism, just how the org-roam package does it. This is + because the variable 'org-id-extra-files' needs to be kept up-to-date + whenever an operation on a file is performed. At scale, this sort of + monitoring requires specialised software. Such a mechanism is outside + the scope of Denote---if you need a db, use org-roam which is already + great. + + [0] + + [1] + + Quote of what I wrote on the GitHub mirror issue 29: + + [ggjp] This is what I was implying. That we are, in fact, + providing an option that is not viable long-term, but keeping + the option for expert users who will be able to handle this. + And we should warn about this clearly in the doc of that option. + + [protesilaos] What you write here @ggjp and what @shrysr explained + tells me that those expert users will need to be real experts. To + put it concretely, I am an experienced Emacs user with no + programming background, who has written several Emacs + packages (including the modus-themes which are built into Emacs), + but I have zero knowledge of using a db or of handling things with + python and the like. So if I opt in to 'denote-link-use-org-id' I + will eventually run into problems that my non-existent skills will + prevent me from solving. At that point, I will just use org-roam + which already handles this use-case in a competent way (and has a + massive community to rely on in case I need further support). + + If each package needs to write its own optimisations and maintain + its own cache, to me this shows that 'org-id' is not good enough for + the time being: more work needs to be done in org.git to provide a + universal solution. + + I wanted to support 'org-id' by default on the premise that Denote + must be a good Emacs citizen which interoperates with the rest of + the wider ecosystem. But if 'org-id' leaves something to be + desired, then that goal is not worth pursuing: we add complexity to + our code, offer an option that we cannot genuinely/adequately + support, and make usage of it contingent on reading the docs and + having a high level of expertise. + + I think being a good Emacs citizen is a laudable principle. In this + case, the right thing to do is to recommend the use of org-roam + instead of trying to accommodate 'org-id'. As such, I have now + changed my mind and think we should remove what we previously added. + + For some context here: the reason I never used org-roam is + because (i) it is Org-specific whereas I write notes in different + file types and (ii) I did not want to ever rely on a db or + equivalent dependency. + + + + README.org | 226 ++++++++--------------------------------------------- + denote-link.el | 99 ++++++----------------- + denote-retrieve.el | 2 +- + denote.el | 14 +--- + 4 files changed, 63 insertions(+), 278 deletions(-) +#+end_example + +Followed up by my explanation: + +#+begin_src text +> can we not have denote style links to be default for (de)notes - and +> explicitly supported, while if they need to, users can still link +> denote org files via org-id to any other notes/files (and vice versa) +> -- in which case performance + testing for org-id driven linking is +> not within Denote's purview at all? + +The formal support for `id:` links was added shortly before the release +of version `0.1.0`. In the days prior, we supported what you describe +via the manual. The user could change the `denote-org-front-matter` +variable to include a `PROPERTIES` drawer. This possibility still +exists, though yesterday I removed the relevant entry from the manual. +This way only the real do-it-yourself experts will go down that path. + +My concern here is with managing expectations. If our Org notes are +superficially the same as org-roam's, an unsuspecting user may think +that Denote is an org-roam lite. We will thus get issues/requests, such +as those already mentioned in this GitHub repo, about migrating from +org-roam to Denote. While there are similarities, Denote is not a +minimalist org-roam and I would not like to encourage the idea of +treating the two as interchangeable. + +Doing things half-way-through is a way to create false expectations. A +package on GNU ELPA must be usable by users of all skill levels. If the +functionality we provide is incomplete and needs to be covered by +user-level tweaks, we are excluding a portion of the user base while +still assuming the maintenance burden. If someone trusts Denote to, +say, write a 1000 notes, we do not want to surprise them after the fact. +Imagine if the reported issues that triggered this change happened 6 +months into one's daily usage of Denote: it wouldn't be nice. + +Setting the right expectations is a matter of responsibility: we let the +user make a more informed choice and show respect for their time. It +also makes it easier for me to keep Denote's scope in check by not +supporting every little extra that Org implements. The premier Org +extension is org-roam: we do not need another one (or, if we do, I am +not the one to implement it). + +,* * * + +Some comments on the `denote:` hyperlink type for Org as they may be +relevant in this context: + +,* It is meant to work like the standard `file:` type. This means that + it links to a file, while it can also have additional search + parameters, as explained in the Org manual. Evaluate: + + (info "(org) Search Options") + +,* It does not read the front matter, but only the file name. You can + create a note as usual, delete all its contents, save it, and try to + link to it from another note. It works. + +,* Exporting now works like the `file:` type for HTML, LaTeX, Texinfo, + and Markdown. Technically, it also supports the ASCII backend but the + format of the output could be tweaked further. + +There may be refinements to be made, which is okay as that is part of a +maintainer's duties. +#+end_src + +* Version 0.1.0 on 2022-06-27 +:PROPERTIES: +:CUSTOM_ID: h:33939747-ad60-4913-a170-4b2f48f139cc +:END: + +The present entry is intended for early adopters of Denote who may have +not caught up with the latest developments. Prospective users are +advised to read the manual: . For +a video demonstration: . + ++ The =denote= package on GNU ELPA will be available a few hours after + this release. GNU ELPA provides the latest stable release. To use a + development snapshot, read: + . + ++ Remember that any significant contribution (above ~15 lines) requires + copyright assignment to the Free Software Foundation. A form with + instructions is included in the manual's "Contributing" section: + . + ++ The front matter of notes in Org has changed to be compliant with the + standard =org-id= infrastructure. A =PROPERTIES= drawer is added to + the top of the file, which includes an =ID= property with the value of + the Denote identifier. Sample: + + #+begin_src org + :PROPERTIES: + :ID: 20220610T202537 + :END: + ,#+title: Sample Org front matter + ,#+date: 2022-06-10 + ,#+filetags: denote testing + #+end_src + ++ The front matter of Markdown (YAML or TOML) and plain text files + remains constant. For completeness, this is how they look: + + #+begin_src md + --- + title: "Sample with Markdown and YAML" + date: 2022-06-10 + tags: denote testing + identifier: "20220610T202021" + --- + #+end_src + + #+begin_src md + +++ + title = "Sample with Markdown and TOML" + date = 2022-06-10 + tags = ["denote", "testing"] + identifier = "20220610T201510" + +++ + #+end_src + + #+begin_example + title: Sample plain text + date: 2022-06-10 + tags: denote testing + identifier: 20220610T202232 + --------------------------- + #+end_example + ++ The integration with =org-id= extends to how linking works. By + default, Denote uses its own custom hyperlink type which starts with + the =denote:= prefix. In Org, it works like the =file:= type. When + the user option ~denote-link-use-org-id~ is non-nil, links from Org + notes to other Org notes will use the standard =id:= type instead. As + this is an Org-specific feature, Denote takes care to use the + major-mode-agnostic =denote:= type when the link targets a non-Org + note. + ++ In Org files the links created by Denote are buttonized automatically. + For Markdown and plain text, we use our own methods. When a link is + inserted it is buttonized outright. To buttonize links in existing + notes while visiting them in a buffer, add/evaluate this (it excludes + Org on its own): + + #+begin_src emacs-lisp + (add-hook 'find-file-hook #'denote-link-buttonize-buffer) + #+end_src + ++ The generation of the backlinks' buffer now uses the built-in =xref= + library instead of relying on a hardcoded call to the =find= + executable. This means that the ~denote-link-backlinks~ command will, + in principle, work properly with all Emacs builds. + ++ Users of Emacs 28 or higher can configure ~xref-search-program~ to + change from the default =grep= to =ripgrep=, =ugrep=, or a + user-defined alternative. + ++ This is the first stable release of Denote. It covers close to 400 + commits starting from 2022-06-04. Denote is the successor to a toy + package of mine, USLS, whose first public version was made available + in early November 2020: . + ++ Thanks to everyone involved in the development of Denote. Code + contributions, bug reports, discussion of ideas, are all valuable. + From A-Z the names mentioned in the manual's "Acknowledgements" + section: Colin McLear, Damien Cassou, Frank Ehmsen, Jack Baty, Kaushal + Modi, Peter Povinec, Sven Seebeck, Ypot. + ++ Sources of Denote: + + + Package name (GNU ELPA): =denote= + + Official manual: + + Change log: + + Git repo on SourceHut: + - Mirrors: + + GitHub: + + GitLab: + + Mailing list: blob - /dev/null blob + 60c3b58af49708709570ca120a7154ed079f17d7 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/README-elpa @@ -0,0 +1,6985 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + DENOTE: SIMPLE NOTES WITH AN EFFICIENT + FILE-NAMING SCHEME + + Protesilaos Stavrou + info@protesilaos.com + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +This manual, written by Protesilaos Stavrou, describes the customization +options for the Emacs package called `denote' (or `denote.el'), and +provides every other piece of information pertinent to it. + +The documentation furnished herein corresponds to stable version 4.0.0, +released on 2025-04-15. Any reference to a newer feature which does not +yet form part of the latest tagged commit, is explicitly marked as such. + +Current development target is 4.1.0-dev. + +⁃ Package name (GNU ELPA): `denote' +⁃ Official manual: +⁃ Change log: +⁃ Git repositories: + ⁃ GitHub: + ⁃ GitLab: +⁃ Video demo: +⁃ Backronyms: Denote Everything Neatly; Omit The Excesses. Don’t Ever + Note Only The Epiphenomenal. + +If you are viewing the README.org version of this file, please note that +the GNU ELPA machinery automatically generates an Info manual out of it. + +Table of Contents +───────────────── + +1. COPYING +2. Installation +.. 1. GNU ELPA package +.. 2. Manual installation +3. Sample configuration +.. 1. Get started with this sample configuration +.. 2. More comprehensive sample configuration +4. Overview +5. Points of entry +.. 1. Standard note creation +..... 1. The `denote-prompts' option +..... 2. The `denote-history-completion-in-prompts' option +..... 3. The `denote-templates' option +..... 4. Convenience commands for note creation +..... 5. The `denote-save-buffers' option +..... 6. The `denote-kill-buffers' option +..... 7. The `denote-date-prompt-use-org-read-date' option +.. 2. Create note using Org capture +.. 3. Create note with specific prompts using Org capture +.. 4. Create note with specific values using Org capture +.. 5. Create a note with the region’s contents +..... 1. A custom `denote-region' that references the source +.. 6. Open an existing note or create it if missing +.. 7. Maintain separate directory silos for notes +..... 1. Make Org export work with silos +.. 8. Exclude certain files from file prompts +.. 9. Exclude certain directories from all operations +.. 10. Exclude certain keywords from being inferred +.. 11. Create a controlled vocabulary for keywords +.. 12. Use Denote commands from the menu bar or context menu +6. Renaming files +.. 1. Rename a single file +..... 1. The `denote-rename-confirmations' option +.. 2. Rename a single file based on its front matter +.. 3. Rename multiple files interactively +.. 4. Rename multiple files at once by asking only for keywords +.. 5. Rename multiple files based on their front matter +.. 6. Rename a file by changing only its file type +.. 7. Rename a file by adding or removing a title interactively +.. 8. Rename a file by adding or removing keywords interactively +.. 9. Rename a file by adding or removing a signature interactively +.. 10. Find duplicate identifiers and put them in a Dired buffer +.. 11. Faces used by rename commands +7. The file-naming scheme +.. 1. Change the order of file name components +.. 2. Sluggification of file name components +.. 3. User-defined sluggification of file name components +..... 1. Custom sluggification to remove non-ASCII characters +.. 4. Features of the file-naming scheme for searching or filtering +8. Front matter +.. 1. Change the front matter format +.. 2. Regenerate front matter +9. Linking notes +.. 1. Add a single direct link using a file name prompt +.. 2. Add a direct link to a file whose contents include the given query +.. 3. Add a query link +.. 4. Insert links to all files matching a query in their file name +.. 5. Insert links to all files matching a query in their contents +.. 6. The `denote-open-link-function' user option +.. 7. The `denote-org-store-link-to-heading' user option +.. 8. Adding direct links to files matching contents +.. 9. Insert links from marked files in Dired +.. 10. Link to an existing note or create a new one +.. 11. The backlinks’ buffer +.. 12. Writing metanotes +.. 13. Visiting linked files via the minibuffer +.. 14. Fontify links in non-Org buffers +.. 15. The `denote-link-description-format' to format link descriptions +10. Choose which commands to prompt for +11. Fontification in Dired +12. Automatically rename Denote buffers +.. 1. The `denote-rename-buffer-format' option +13. Use Org dynamic blocks +14. Display filtered and sorted files with `denote-sort-dired' or `denote-dired' +.. 1. Configure what extra prompts `denote-sort-dired' issues +.. 2. Define a sorting function per component +..... 1. Sort signatures that include Luhmann-style sequences +15. Use `denote-grep' to search inside files +16. Interact with the links buffer +17. Minibuffer histories +18. Packages that build on Denote +.. 1. Use the `consult-denote' package for enhanced minibuffer interactions +.. 2. Sequence notes +.. 3. Use the `denote-markdown' package to better integrate Markdown with Denote +.. 4. Use the `denote-journal' package which was formerly `denote-journal-extras.el' +.. 5. Use the `denote-silo' package which formerly was `denote-silo-extras.el' +.. 6. Use the `denote-search' package as a search interface +.. 7. Use the `denote-explore' package to explore your notes +.. 8. Use the `citar-denote' package for bibliography notes +.. 9. Use the `consult-notes' package +.. 10. Use the `denote-menu' package +.. 11. Use the `denote-zettel-interface' package +19. Extending Denote +.. 1. Access the data of the latest note +.. 2. Create a new note in any directory +.. 3. Find empty notes and put them in a Dired buffer +.. 4. Automatically rename the note after saving it +.. 5. Narrow the list of files in Dired +.. 6. Use `dired-virtual-mode' for arbitrary file listings +.. 7. Use Embark to collect minibuffer candidates +.. 8. Search file contents +.. 9. Bookmark the directory with the notes +.. 10. Treat your notes as a project +.. 11. Use the tree-based file prompt for select commands +.. 12. Rename files with Denote in the Image Dired thumbnails buffer +.. 13. Rename files with Denote using `dired-preview' +.. 14. Avoid duplicate identifiers when exporting Denote notes +..... 1. Export Denote notes with Org Mode +..... 2. Export Denote notes with Markdown +.. 15. Set up your workflow for daily or weekly meeting notes +20. For developers or advanced users +.. 1. Common building blocks for developers or advanced users +.. 2. File path interface for developers or advanced users +.. 3. Data retrieval interface for developers or advanced users +.. 4. Prompt interface for developers or advanced users +.. 5. Front matter interface for developers or advanced users +.. 6. Link interface for developers or advanced users +.. 7. Xref interface for developers or advanced users +.. 8. Renaming files interface for developers or advanced users +21. Troubleshoot Denote in a pristine environment +22. Contributing +.. 1. Wishlist of what we can do to extend Denote +23. Publications about Denote +24. Alternatives to Denote +.. 1. Alternative implementations and further reading +25. Frequently Asked Questions +.. 1. Why develop Denote when PACKAGE already exists? +.. 2. Why not rely exclusively on Org? +.. 3. Why care about Unix tools when you use Emacs? +.. 4. Why many small files instead of few large ones? +.. 5. Does Denote perform well at scale? +.. 6. I add TODOs to my notes; will many files slow down the Org agenda? +.. 7. I want to sort by last modified in Dired, why won’t Denote let me? +.. 8. How do you handle the last modified case? +.. 9. Why are some Org links opening outside Emacs? +.. 10. Speed up backlinks’ or query links’ buffer creation? +.. 11. Why do I get “Search failed with status 1” when I search for backlinks? +.. 12. Why do I get a double `#+title' in Doom Emacs? +26. Acknowledgements +27. GNU Free Documentation License +28. Indices +.. 1. Function index +.. 2. Variable index +.. 3. Concept index + + +1 COPYING +═════════ + + Copyright (C) 2022-2025 Free Software Foundation, Inc. + + Permission is granted to copy, distribute and/or modify + this document under the terms of the GNU Free + Documentation License, Version 1.3 or any later version + published by the Free Software Foundation; with no + Invariant Sections, with the Front-Cover Texts being “A + GNU Manual,” and with the Back-Cover Texts as in (a) + below. A copy of the license is included in the section + entitled “GNU Free Documentation License.” + + (a) The FSF’s Back-Cover Text is: “You have the freedom to + copy and modify this GNU manual.” + + +2 Installation +══════════════ + + + + +2.1 GNU ELPA package +──────────────────── + + The package is available as `denote'. Simply do: + + ┌──── + │ M-x package-refresh-contents + │ M-x package-install + └──── + + + And search for it. + + GNU ELPA provides the latest stable release. Those who prefer to + follow the development process in order to report bugs or suggest + changes, can use the version of the package from the GNU-devel ELPA + archive. Read: + . + + +2.2 Manual installation +─────────────────────── + + Assuming your Emacs files are found in `~/.emacs.d/', execute the + following commands in a shell prompt: + + ┌──── + │ cd ~/.emacs.d + │ + │ # Create a directory for manually-installed packages + │ mkdir manual-packages + │ + │ # Go to the new directory + │ cd manual-packages + │ + │ # Clone this repo, naming it "denote" + │ git clone https://github.com/protesilaos/denote denote + └──── + + Finally, in your `init.el' (or equivalent) evaluate this: + + ┌──── + │ ;; Make Elisp files in that directory available to the user. + │ (add-to-list 'load-path "~/.emacs.d/manual-packages/denote") + └──── + + Everything is in place to set up the package. + + +3 Sample configuration +══════════════════════ + + Denote is immediately useful for beginners and power users alike. This + manual covers everything in detail, though do not let the numerous + possibilities distract you from the fact that a basic configuration is + enough to be highly productive ([Get started with this sample + configuration]). + + +[Get started with this sample configuration] See section 3.1 + +3.1 Get started with this sample configuration +────────────────────────────────────────────── + + If you are new to Denote, this a good place to start. Then work your + way through the manual and expand your configuration accordingly. Only + include commands/variables that are useful to you. We provide another + code sample if you need some ideas ([More comprehensive sample + configuration]). + + ┌──── + │ ;; Remember that the website version of this manual shows the latest + │ ;; developments, which may not be available in the package you are + │ ;; using. Instead of copying from the web site, refer to the version + │ ;; of the documentation that comes with your package. Evaluate: + │ ;; + │ ;; (info "(denote) Sample configuration") + │ (use-package denote + │ :ensure t + │ :hook (dired-mode . denote-dired-mode) + │ :bind + │ (("C-c n n" . denote) + │ ("C-c n r" . denote-rename-file) + │ ("C-c n l" . denote-link) + │ ("C-c n b" . denote-backlinks) + │ ("C-c n d" . denote-dired) + │ ("C-c n g" . denote-grep)) + │ :config + │ (setq denote-directory (expand-file-name "~/Documents/notes/")) + │ + │ ;; Automatically rename Denote buffers when opening them so that + │ ;; instead of their long file name they have, for example, a literal + │ ;; "[D]" followed by the file's title. Read the doc string of + │ ;; `denote-rename-buffer-format' for how to modify this. + │ (denote-rename-buffer-mode 1)) + └──── + + +[More comprehensive sample configuration] See section 3.2 + + +3.2 More comprehensive sample configuration +─────────────────────────────────────────── + + Here we include more of what you can configure with Denote ([Get + started with this sample configuration]). + + ┌──── + │ ;; Remember that the website version of this manual shows the latest + │ ;; developments, which may not be available in the package you are + │ ;; using. Instead of copying from the web site, refer to the version + │ ;; of the documentation that comes with your package. Evaluate: + │ ;; + │ ;; (info "(denote) Sample configuration") + │ (use-package denote + │ :ensure t + │ :hook + │ ( ;; If you use Markdown or plain text files, then you want to make + │ ;; the Denote links clickable (Org renders links as buttons right + │ ;; away) + │ (text-mode . denote-fontify-links-mode-maybe) + │ ;; Apply colours to Denote names in Dired. This applies to all + │ ;; directories. Check `denote-dired-directories' for the specific + │ ;; directories you may prefer instead. Then, instead of + │ ;; `denote-dired-mode', use `denote-dired-mode-in-directories'. + │ (dired-mode . denote-dired-mode)) + │ :bind + │ ;; Denote DOES NOT define any key bindings. This is for the user to + │ ;; decide. For example: + │ ( :map global-map + │ ("C-c n n" . denote) + │ ("C-c n d" . denote-dired) + │ ("C-c n g" . denote-grep) + │ ;; If you intend to use Denote with a variety of file types, it is + │ ;; easier to bind the link-related commands to the `global-map', as + │ ;; shown here. Otherwise follow the same pattern for `org-mode-map', + │ ;; `markdown-mode-map', and/or `text-mode-map'. + │ ("C-c n l" . denote-link) + │ ("C-c n L" . denote-add-links) + │ ("C-c n b" . denote-backlinks) + │ ("C-c n q c" . denote-query-contents-link) ; create link that triggers a grep + │ ("C-c n q f" . denote-query-filenames-link) ; create link that triggers a dired + │ ;; Note that `denote-rename-file' can work from any context, not just + │ ;; Dired bufffers. That is why we bind it here to the `global-map'. + │ ("C-c n r" . denote-rename-file) + │ ("C-c n R" . denote-rename-file-using-front-matter) + │ + │ ;; Key bindings specifically for Dired. + │ :map dired-mode-map + │ ("C-c C-d C-i" . denote-dired-link-marked-notes) + │ ("C-c C-d C-r" . denote-dired-rename-files) + │ ("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords) + │ ("C-c C-d C-R" . denote-dired-rename-marked-files-using-front-matter)) + │ + │ :config + │ ;; Remember to check the doc string of each of those variables. + │ (setq denote-directory (expand-file-name "~/Documents/notes/")) + │ (setq denote-save-buffers nil) + │ (setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) + │ (setq denote-infer-keywords t) + │ (setq denote-sort-keywords t) + │ (setq denote-prompts '(title keywords)) + │ (setq denote-excluded-directories-regexp nil) + │ (setq denote-excluded-keywords-regexp nil) + │ (setq denote-rename-confirmations '(rewrite-front-matter modify-file-name)) + │ + │ ;; Pick dates, where relevant, with Org's advanced interface: + │ (setq denote-date-prompt-use-org-read-date t) + │ + │ ;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. + │ (denote-rename-buffer-mode 1)) + └──── + + +[Get started with this sample configuration] See section 3.1 + + +4 Overview +══════════ + + Denote aims to be a simple-to-use, focused-in-scope, and effective + note-taking and file-naming tool for Emacs. + + Denote is based on the idea that files should follow a predictable and + descriptive file-naming scheme. The file name must offer a clear + indication of what the contents are about, without reference to any + other metadata. Denote basically streamlines the creation of such + files or file names while providing facilities to link between them + (where those files are editable). + + Denote’s file-naming scheme is not limited to “notes”. It can be used + for all types of file, including those that are not editable in Emacs, + such as videos. Naming files in a consistent way makes their + filtering and retrieval considerably easier. Denote provides relevant + facilities to rename files, regardless of file type. + + Denote is based on the following core design principles: + + Predictability + File names must follow a consistent and descriptive naming + convention ([The file-naming scheme]). The file name alone + should offer a clear indication of what the contents are, + without reference to any other metadatum. This convention is + not specific to note-taking, as it is pertinent to any form of + file that is part of the user’s long-term storage ([Renaming + files]). + + Composability + Be a good Emacs citizen, by integrating with other packages or + built-in functionality instead of re-inventing functions such as + for filtering or greping. The author of Denote (Protesilaos, + aka “Prot”) writes ordinary notes in plain text (`.txt'), + switching on demand to an Org file only when its expanded set of + functionality is required for the task at hand ([Points of + entry]). + + Portability + Notes are plain text and should remain portable. The way Denote + writes file names, the front matter it includes in the note’s + header, and the links it establishes must all be adequately + usable with standard Unix tools. No need for a database or some + specialised software. As Denote develops and this manual is + fully fleshed out, there will be concrete examples on how to do + the Denote-equivalent on the command-line. + + Flexibility + Do not assume the user’s preference for a note-taking + methodology. Denote is conceptually similar to the Zettelkasten + Method, which you can learn more about in this detailed + introduction: . Notes + are atomic (one file per note) and have a unique identifier. + However, Denote does not enforce a particular methodology for + knowledge management, such as a restricted vocabulary or + mutually exclusive sets of keywords. Denote also does not check + if the user writes thematically atomic notes. It is up to the + user to apply the requisite rigor and/or creativity in pursuit + of their preferred workflow ([Writing metanotes]). + + Hackability + Denote’s code base consists of small and reusable functions. + They all have documentation strings. The idea is to make it + easier for users of varying levels of expertise to understand + what is going on and make surgical interventions where necessary + (e.g. to tweak some formatting). In this manual, we provide + concrete examples on such user-level configurations ([Keep a + journal or diary]). + + Now the important part… “Denote” is the familiar word, though it also + is a play on the “note” concept. Plus, we can come up with acronyms, + recursive or otherwise, of increasingly dubious utility like: + + ⁃ Don’t Ever Note Only The Epiphenomenal + ⁃ Denote Everything Neatly; Omit The Excesses + + But we’ll let you get back to work. Don’t Eschew or Neglect your + Obligations, Tasks, and Engagements. + + +[The file-naming scheme] See section 7 + +[Renaming files] See section 6 + +[Points of entry] See section 5 + +[Writing metanotes] See section 9.12 + +[Keep a journal or diary] See section 18.4 + + +5 Points of entry +═════════════════ + + There are seven main ways to write a note with Denote: invoke the + `denote', `denote-type', `denote-date', `denote-subdirectory', + `denote-template', `denote-signature' commands, or leverage the + `org-capture-templates' by setting up a template which calls the + function `denote-org-capture'. We explain all of those in the + subsequent sections. Other more specialised commands exist as well, + which one shall learn about as they read through this manual. We do + not want to overwhelm the user with options at this stage. + + All these commands construct the file name in accordance with the user + option `denote-file-name-components-order' ([Change the order of file + name components]). + + +[Change the order of file name components] See section 7.1 + +5.1 Standard note creation +────────────────────────── + + The `denote' command will prompt for a title. If a region is active, + the text of the region becomes the default at the minibuffer prompt + (meaning that typing `RET' without any input will use the default + value). Once the title is supplied, the `denote' command will then + ask for keywords. The resulting note will have a file name as already + explained: [The file naming scheme] + + The `denote' command runs the hook `denote-after-new-note-hook' after + creating the new note ([Access the data of the latest note]). When + called from Lisp, it returns the path it generates. Before returning + the path, it decides what to do with the buffer of the note, in + accordance with the user option `denote-kill-buffers' ([The + `denote-kill-buffers' option]). + + The file type of the new note is determined by the user option + `denote-file-type' ([Front matter]). + + The keywords’ prompt supports minibuffer completion. Available + candidates are those defined in the user option + `denote-known-keywords'. More candidates can be inferred from the + names of existing notes, by setting `denote-infer-keywords' to non-nil + (which is the case by default) ([Create a controlled vocabulary for + keywords]). + + Multiple keywords can be inserted by separating them with a comma (or + whatever the value of the `crm-separator' is—which should be a comma). + When the user option `denote-sort-keywords' is non-nil (the default), + keywords are sorted alphabetically (technically, the sorting is done + with `string-lessp'). + + The interactive behaviour of the `denote' command is influenced by the + user option `denote-prompts' ([The denote-prompts option]). + + The `denote' command can also be called from Lisp. Read its doc + string for the technicalities. + + In the interest of discoverability, `denote' is also available under + the alias `denote-create-note'. + + +[The file naming scheme] See section 7 + +[Access the data of the latest note] See section 19.1 + +[The `denote-kill-buffers' option] See section 5.1.6 + +[Front matter] See section 8 + +[Create a controlled vocabulary for keywords] See section 5.11 + +[The denote-prompts option] See section 5.1.1 + +5.1.1 The `denote-prompts' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-prompts' determines how the `denote' command + will behave interactively ([Standard note creation]). + + Commands that prompt for user input to construct a Denote file name + include, but are not limited to: `denote', `denote-signature', + `denote-type', `denote-date', `denote-subdirectory', + `denote-rename-file', `denote-dired-rename-files'. + + • [Convenience commands for note creation]. + • [Renaming files]. + + The value of this user option is a list of symbols, which includes any + of the following: + + • `title': Prompt for the title of the new note ([The + `denote-history-completion-in-prompts' option]). + + • `keywords': Prompts with completion for the keywords of the new + note. Available candidates are those specified in the user option + `denote-known-keywords'. If the user option `denote-infer-keywords' + is non-nil, keywords in existing note file names are included in the + list of candidates. The `keywords' prompt uses + `completing-read-multiple', meaning that it can accept multiple + keywords separated by a comma (or whatever the value of + `crm-separator' is). + + • `file-type': Prompts with completion for the file type of the new + note. Available candidates are those specified in the user option + `denote-file-type'. Without this prompt, `denote' uses the value of + `denote-file-type'. + + • `subdirectory': Prompts with completion for a subdirectory in which + to create the note. Available candidates are the value of the user + option `denote-directory' and all of its subdirectories. Any + subdirectory must already exist: Denote will not create it. + + • `date': Prompts for the date of the new note. It will expect an + input like 2022-06-16 or a date plus time: 2022-06-16 14:30. + Without the `date' prompt, the `denote' command uses the + `current-time'. + + [The denote-date-prompt-use-org-read-date option]. + + • `template': Prompts for a KEY among the `denote-templates'. The + value of that KEY is used to populate the new note with content, + which is added after the front matter ([The denote-templates + option]). + + • `signature': - Prompts for an arbitrary string that can be used for + any kind of workflow, such as a special tag to label the `part1' and + `part2' of a large file that is split in half, or to add special + contexts like `home' and `work', or even priorities like `a', `b', + `c'. One other use-case is to implement a sequencing scheme that + makes notes have hierarchical relationships. This is handled by our + optional extension `denote-sequence.el', which is part of the + `denote' package ([Write sequence notes or “folgezettel”]). + + The prompts occur in the given order. + + If the value of this user option is nil, no prompts are used. The + resulting file name will consist of an identifier (i.e. the date and + time) and a supported file type extension (per `denote-file-type'). + + Recall that Denote’s standard file-naming scheme is defined as follows + ([The file-naming scheme]): + + ┌──── + │ DATE--TITLE__KEYWORDS.EXT + └──── + + + If either or both of the `title' and `keywords' prompts are not + included in the value of this variable, file names will be any of + those permutations: + + ┌──── + │ DATE.EXT + │ DATE--TITLE.EXT + │ DATE__KEYWORDS.EXT + └──── + + + When in doubt, always include the `title' and `keywords' prompts. + + Finally, this user option only affects the interactive use of the + `denote' or other relevant commands (advanced users can call it from + Lisp). In Lisp usage, the behaviour is always what the caller + specifies, based on the supplied arguments. + + +[Standard note creation] See section 5.1 + +[Convenience commands for note creation] See section 5.1.4 + +[Renaming files] See section 6 + +[The `denote-history-completion-in-prompts' option] See section 5.1.2 + +[The denote-date-prompt-use-org-read-date option] See section 5.1.7 + +[The denote-templates option] See section 5.1.3 + +[Write sequence notes or “folgezettel”] See section 18.2 + +[The file-naming scheme] See section 7 + + +5.1.2 The `denote-history-completion-in-prompts' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-history-completion-in-prompts' toggles history + completion in all `denote-prompts-with-history-as-completion'. + + When this user option is set to a non-nil value, Denote will use + minibuffer history entries as completion candidates in all of the + `denote-prompts-with-history-as-completion'. Those will show previous + inputs from their respective history as possible values to select, + either to (i) re-insert them verbatim or (ii) with the intent to edit + them further (depending on the minibuffer user interface, one can + select a candidate with `TAB' without exiting the minibuffer, as + opposed to what `RET' normally does by selecting and exiting). + + When this user option is set to a nil value, all of the + `denote-prompts-with-history-as-completion' will not use minibuffer + completion: they will just prompt for a string of characters. Their + history is still available through all the standard ways of retrieving + minibuffer history, such as with the command + `previous-history-element'. + + History completion still allows arbitrary values to be provided as + input: they do not have to match the available minibuffer completion + candidates. + + Note that some prompts, like `denote-keywords-prompt', always use + minibuffer completion, due to the specifics of their data. + + [ Consider enabling the built-in `savehist-mode' to persist minibuffer + histories between sessions.] + + +5.1.3 The `denote-templates' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-templates' is an alist of content templates + for new notes. A template is arbitrary text that Denote will add to a + newly created note right below the front matter. + + Templates are expressed as a `(KEY . VALUE)' association. + + • The `KEY' is the name which identifies the template. It is an + arbitrary symbol, such as `report', `memo', `statement'. + + • The `VALUE' is either a string or the symbol of a function. + + • If it is a string, it is ordinary text that Denote will insert + as-is. It can contain newline characters to add spacing. The + manual of Denote contains examples on how to use the `concat' + function, beside writing a generic string. + + • If it is a function, it is called without arguments and is + expected to return a string. Denote will call the function and + insert the result in the buffer. + + The user can choose a template either by invoking the command + `denote-template' or by changing the user option `denote-prompts' to + always prompt for a template when calling the `denote' command. + + [The denote-prompts option]. + + [Convenience commands for note creation]. + + Templates can be written directly as one large string. For example + (the `\n' character is read as a newline): + + ┌──── + │ (setq denote-templates + │ '((report . "* Some heading\n\n* Another heading") + │ (memo . "* Some heading + │ + │ * Another heading + │ + │ "))) + └──── + + Long strings may be easier to type but interpret indentation + literally. Also, they do not scale well. A better way is to use some + Elisp code to construct the string. This would typically be the + `concat' function, which joins multiple strings into one. The + following is the same as the previous example: + + ┌──── + │ (setq denote-templates + │ `((report . "* Some heading\n\n* Another heading") + │ (memo . ,(concat "* Some heading" + │ "\n\n" + │ "* Another heading" + │ "\n\n")))) + └──── + + Notice that to evaluate a function inside of an alist we use the + backtick to quote the alist (NOT the straight quote) and then prepend + a comma to the expression that should be evaluated. The `concat' form + here is not sensitive to indentation, so it is easier to adjust for + legibility. + + For when the `VALUE' is a function, we have this: + + ┌──── + │ (setq denote-templates + │ `((report . "* Some heading\n\n* Another heading") + │ (blog . my-denote-template-function-for-blog) ; a function to return a string + │ (memo . ,(concat "* Some heading" + │ "\n\n" + │ "* Another heading" + │ "\n\n")))) + └──── + + In this example, `my-denote-template-function-for-blog' is a function + that returns a string. Denote will take care to insert it in the + buffer. + + DEV NOTE: We do not provide more examples at this point, though feel + welcome to ask for help if the information provided herein is not + sufficient. We shall expand the manual accordingly. + + +[The denote-prompts option] See section 5.1.1 + +[Convenience commands for note creation] See section 5.1.4 + + +5.1.4 Convenience commands for note creation +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Sometimes the user needs to create a note that has different + requirements from those of `denote' ([Standard note creation]). While + this can be achieved globally by changing the `denote-prompts' user + option, there are cases where an ad-hoc method is the appropriate one + ([The denote-prompts option]). + + To this end, Denote provides the following interactive convenience + commands for note creation. They all work by appending a new prompt to + the existing `denote-prompts'. + + Create note by specifying file type + The `denote-type' command creates a note while prompting for a + file type. + + This is the equivalent of calling `denote' when `denote-prompts' + has the `file-type' prompt appended to its existing prompts. In + practical terms, this lets you produce, say, a note in Markdown + even though you normally write in Org ([Standard note + creation]). + + The `denote-create-note-using-type' is an alias of + `denote-type'. + + Create note using a date + Normally, Denote reads the current date and time to construct + the unique identifier of a newly created note ([Standard note + creation]). Sometimes, however, the user needs to set an + explicit date+time value. + + This is where the `denote-date' command comes in. It creates a + note while prompting for a date. The date can be in + YEAR-MONTH-DAY notation like `2022-06-30' or that plus the time: + `2022-06-16 14:30'. + + [The denote-date-prompt-use-org-read-date option]. + + This is the equivalent of calling `denote' when `denote-prompts' + has the `date' prompt appended to its existing prompts. + + The `denote-create-note-using-date' is an alias of + `denote-date'. + + Create note in a specific directory + The `denote-subdirectory' command creates a note while prompting + for a subdirectory. Available candidates include the value of + the variable `denote-directory' and any subdirectory thereof + (Denote does not create subdirectories). + + This is the equivalent of calling `denote' when `denote-prompts' + has the `subdirectory' prompt appended to its existing prompts. + + The `denote-create-note-in-subdirectory' is a more descriptive + alias of `denote-subdirectory'. + + Create note and add a template + The `denote-template' command creates a new note and inserts the + specified template below the front matter ([The denote-templates + option]). Available candidates for templates are specified in + the user option `denote-templates'. + + This is the equivalent of calling `denote' when `denote-prompts' + has the `template' prompt appended to its existing prompts. + + The `denote-create-note-with-template' is an alias of the + command `denote-template', meant to help with discoverability. + + Create note with a signature + The `denote-signature' command first prompts for an arbitrary + string to use in the optional `SIGNATURE' field of the file name + and then asks for a title and keywords. Signatures are + arbitrary strings of alphanumeric characters which can be used + to establish sequential relations between file at the level of + their file name (e.g. 1, 1a, 1b, 1b1, 1b2, …). + + This is the equivalent of calling `denote' when `denote-prompts' + has the `signature' prompt appended to its existing prompts. + + The `denote-create-note-using-signature' is an alias of the + command `denote-signature' intended to make the functionality + more discoverable. + + +[Standard note creation] See section 5.1 + +[The denote-prompts option] See section 5.1.1 + +[The denote-date-prompt-use-org-read-date option] See section 5.1.7 + +[The denote-templates option] See section 5.1.3 + +◊ 5.1.4.1 Write your own convenience commands + + The convenience commands we provide only cover some basic use-cases + ([Convenience commands for note creation]). The user may require + combinations that are not covered, such as to prompt for a template + and for a subdirectory, instead of only one of the two. To this end, + we show how to follow the code we use in Denote to write your own + variants of those commands. + + First let’s take a look at the definition of one of those commands. + They all look the same, but we use `denote-subdirectory' for this + example: + + ┌──── + │ (defun denote-subdirectory () + │ "Create note while prompting for a subdirectory. + │ + │ Available candidates include the value of the variable + │ `denote-directory' and any subdirectory thereof. + │ + │ This is the equivalent of calling `denote' when `denote-prompts' + │ has the `subdirectory' prompt appended to its existing prompts." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-prompts (denote-add-prompts '(subdirectory)))) + │ (call-interactively #'denote))) + └──── + + The hyphenated word after `defun' is the name of the function. It has + to be unique. Then we have the documentation string (or “doc string”) + which is for the user’s convenience. + + This function is `interactive', meaning that it can be called via + `M-x' or be assigned to a key binding. Then we have the local binding + of the `denote-prompts' to the desired combination (“local” means + specific to this function without affecting other contexts). Lastly, + it calls the standard `denote' command interactively, so it uses all + the prompts in their specified order. + + The function call `(denote-add-prompts '(subdirectory))' will append + the subdirectory prompt to the existing value of the `denote-prompts'. + If, for example, the default value is `'(title keywords)' (to prompt + for a title and then for keywords), it will become `'(subdirectory + title keywords)' inside the context of this `let'. Remember that this + is “local”, so the global value of `denote-prompts' remains + unaffected. + + Now let’s say we want to have a command that (i) asks for a template + (ii) for a subdirectory ([The denote-templates option]), and (iii) + then goes through the remaining `denote-prompts'. All we need to do is + tweak the `let' bound value of `denote-prompts' and give our command a + unique name: + + ┌──── + │ ;; Like `denote-subdirectory' but also ask for a template + │ (defun my-denote-subdirectory-with-template () + │ "Create note while also prompting for a template and subdirectory. + │ + │ This is the equivalent of calling `denote' when `denote-prompts' has the + │ `subdirectory' and `template' prompts appended to its existing prompts." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-prompts (denote-add-prompts '(subdirectory template)))) + │ (call-interactively #'denote))) + └──── + + The tweaks to `denote-prompts' determine how the command will behave + ([The denote-prompts option]). Use this paradigm to write your own + variants which you can then assign to keys, invoke with `M-x', or add + to the list of commands available at the `denote-command-prompt' + ([Choose which commands to prompt for]). + + In the above scenario, we are using the `denote-add-prompts' function, + which appends whatever prompts we want to the existing value of + `denote-prompts'. If the user prefers to completely override the + `denote-prompts', they can set the value outright: + + ┌──── + │ (defun my-denote-subdirectory-with-template-title-and-keywords () + │ "Create a note while prompting for subdirectory, template, title, and keywords. + │ + │ This is the equivalent of calling `denote' when `denote-prompts' has the + │ value '(template subdirectory title keywords)." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-prompts '(subdirectory template title keywords))) + │ (call-interactively #'denote))) + └──── + + + [Convenience commands for note creation] See section 5.1.4 + + [The denote-templates option] See section 5.1.3 + + [The denote-prompts option] See section 5.1.1 + + [Choose which commands to prompt for] See section 10 + + +5.1.5 The `denote-save-buffers' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-save-buffer-after-creation' controls whether + commands that create new notes save their buffer outright. + + The default behaviour of commands such as `denote' (or related) is to + not save the buffer they create ([Points of entry]). This gives the + user the chance to review the text before writing it to a file. The + user may choose to delete the unsaved buffer, thus not creating a new + note ([The `denote-save-buffer-after-creation' option]). + + This option also applies to notes affected by the renaming commands + (`denote-rename-file' and related). + + If this user option is set to a non-nil value, such buffers are saved + automatically. The assumption is that the user who opts in to this + feature is familiar with the `denote-rename-file' operation (or + related) and knows it is reliable ([Renaming files]). + + [The `denote-kill-buffers' option]. + + +[Points of entry] See section 5 + +[The `denote-save-buffer-after-creation' option] See section 5.1.5 + +[Renaming files] See section 6 + +[The `denote-kill-buffers' option] See section 5.1.6 + + +5.1.6 The `denote-kill-buffers' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-kill-buffers' controls whether to kill a + buffer that was generated by a Denote command. This can happen when + creating a new file or renaming an existing one. + + • [Points of entry]. + • [Renaming files]. + + The default behaviour of creation or renaming commands such as + `denote' or `denote-rename-file' is to not kill the buffer they create + or modify at the end of their operation. The idea is to give the user + the chance to confirm that everything is in order. + + If this user option is nil (the default), buffers affected by a + creation or renaming command are not automatically killed. + + If set to the symbol `on-creation', new notes are automatically + killed. + + If set to the symbol `on-rename', renamed notes are automatically + killed. + + If set to t, new and renamed notes are killed. + + If a buffer is killed, it is also saved, as if `denote-save-buffers' + were t ([The `denote-save-buffers' option]). + + In all cases, if the buffer already existed before the Denote + operation it is NOT automatically killed. + + +[Points of entry] See section 5 + +[Renaming files] See section 6 + +[The `denote-save-buffers' option] See section 5.1.5 + + +5.1.7 The `denote-date-prompt-use-org-read-date' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + By default, Denote uses its own simple prompt for date or date+time + input ([The denote-prompts option]). This is done when the + `denote-prompts' option includes a `date' symbol and/or when the user + invokes the `denote-date' command. + + Users who want to benefit from the more advanced date selection method + that is common in interactions with Org mode, can set the user option + `denote-date-prompt-use-org-read-date' to a non-nil value. + + +[The denote-prompts option] See section 5.1.1 + + +5.2 Create note using Org capture +───────────────────────────────── + + For integration with `org-capture', the user must first add the + relevant template. Such as: + + ┌──── + │ (with-eval-after-load 'org-capture + │ (add-to-list 'org-capture-templates + │ '("n" "New note (with Denote)" plain + │ (file denote-last-path) + │ #'denote-org-capture + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t))) + └──── + + Once the template is added, it is accessed from the specified key. If, + for instance, `org-capture' is bound to `C-c c', then the note + creation is initiated with `C-c c n', per the above snippet. After + that, the process is the same as with invoking `denote' directly, + namely: a prompt for a title followed by a prompt for keywords, + assuming the default settings ([Standard note creation]). Concretely, + this method always respects the value of the user option + `denote-prompts' ([The `denote-prompts' option]). + + It is also possible to define templates that have specific prompts or + certain values set, for which there is no prompt: + + • [Create note with specific values using Org capture] + • [Create note with specific prompts using Org capture] + + Users may prefer to leverage `org-capture' in order to extend file + creation with the specifiers described in the `org-capture-templates' + documentation (such as to capture the active region and/or create a + hyperlink pointing to the given context). + + IMPORTANT. Due to the particular file-naming scheme of Denote, which + is derived dynamically, such specifiers or other arbitrary text cannot + be written directly in the template. Instead, they have to be + assigned to the user option `denote-org-capture-specifiers', which is + interpreted by the function `denote-org-capture'. Example with our + default value: + + ┌──── + │ (setq denote-org-capture-specifiers "%l\n%i\n%?") + └──── + + Note that `denote-org-capture' ignores the `denote-file-type': it + always sets the Org file extension for the created note to ensure that + the capture process works as intended, especially for the desired + output of the `denote-org-capture-specifiers'. + + [ You may not need `org-capture' to do what you want ([Write your own + convenience commands]). ] + + +[Standard note creation] See section 5.1 + +[The `denote-prompts' option] See section 5.1.1 + +[Create note with specific values using Org capture] See section 5.4 + +[Create note with specific prompts using Org capture] See section 5.3 + +[Write your own convenience commands] See section 5.1.4.1 + + +5.3 Create note with specific prompts using Org capture +─────────────────────────────────────────────────────── + + This section assumes knowledge of how Denote+org-capture work, as + explained in the previous section ([Create note using Org capture]). + + The previous section shows how to define an Org capture template that + always prompts for whatever is set in the user option `denote-prompts' + (title and keywords, by default). There are, however, cases where the + user wants more control over what kind of input Denote will prompt + for. To this end, we provide the function + `denote-org-capture-with-prompts'. Below we explain it and then show + some examples of how to use it. + + The `denote-org-capture-with-prompts' is like `denote-org-capture' but + with optional prompt parameters. + + When called without arguments, it does not prompt for anything. It + just returns the front matter with title and keyword fields empty and + the date and identifier fields specified. It also makes the file name + consist of only the identifier plus the Org file name extension ([The + file-naming scheme]). + + Otherwise, it produces a minibuffer prompt for every non-nil value + that corresponds to the `TITLE', `KEYWORDS', `SUBDIRECTORY', `DATE', + and `TEMPLATE' arguments. The prompts are those used by the standard + `denote' command and all of its utility commands ([Points of entry]). + + When returning the contents that fill in the Org capture template, the + sequence is as follows: front matter, `TEMPLATE', and then the value + of the user option `denote-org-capture-specifiers'. + + Important note: in the case of `SUBDIRECTORY' actual subdirectories + must exist—Denote does not create them. Same principle for `TEMPLATE' + as templates must exist and are specified in the user option + `denote-templates'. + + This is how one can incorporate `denote-org-capture-with-prompts' in + their Org capture templates. Instead of passing a generic `t' which + makes it hard to remember what the argument means, we use semantic + keywords like `:title' for our convenience (internally this does not + matter as the value still counts as non-nil, so `:foo' for `TITLE' is + treated the same as `:title' or `t'). + + ┌──── + │ ;; This prompts for TITLE, KEYWORDS, and SUBDIRECTORY + │ (add-to-list 'org-capture-templates + │ '("N" "New note with prompts (with denote.el)" plain + │ (file denote-last-path) + │ (function + │ (lambda () + │ (denote-org-capture-with-prompts :title :keywords :subdirectory))) + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t)) + │ + │ ;; This prompts only for SUBDIRECTORY + │ (add-to-list 'org-capture-templates + │ '("N" "New note with prompts (with denote.el)" plain + │ (file denote-last-path) + │ (function + │ (lambda () + │ (denote-org-capture-with-prompts nil nil :subdirectory))) + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t)) + │ + │ ;; This prompts for TITLE and SUBDIRECTORY + │ (add-to-list 'org-capture-templates + │ '("N" "New note with prompts (with denote.el)" plain + │ (file denote-last-path) + │ (function + │ (lambda () + │ (denote-org-capture-with-prompts :title nil :subdirectory))) + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t)) + └──── + + [ You may not need `org-capture' to do what you want ([Write your own + convenience commands]). ] + + +[Create note using Org capture] See section 5.2 + +[The file-naming scheme] See section 7 + +[Points of entry] See section 5 + +[Write your own convenience commands] See section 5.1.4.1 + + +5.4 Create note with specific values using Org capture +────────────────────────────────────────────────────── + + The ordinary procedure to create a note with `org-capture' respects + the value of the user option `denote-prompts' ([Create note using Org + capture]): the user is prompted for all the values they have + configured (title and keywords, by default). Sometimes, there is no + need to have a certain prompt because the value of it will be + constant. For example, the user wants to have a template that (i) + respects the `denote-prompts' but (ii) puts the new note in an + existing subdirectory of the `denote-directory'. The following code + block does exactly that. + + [ It also is possible to have a template that deviates from + `denote-prompts' and prompts for specific values ([Create note with + specific prompts using Org capture]). ] + + ┌──── + │ (with-eval-after-load 'org-capture + │ (add-to-list 'org-capture-templates + │ '("r" "New reference (with Denote)" plain + │ (file denote-last-path) + │ (function + │ (lambda () + │ (let ((denote-use-directory (expand-file-name "reference" (denote-directory)))) + │ (denote-org-capture)))) + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t))) + └──── + + The values one may predefine in this way are via these variables ([For + developers or advanced users]): + + ⁃ `denote-use-date' + + ⁃ `denote-use-directory' + + ⁃ `denote-use-file-type' + + ⁃ `denote-use-keywords' + + ⁃ `denote-use-signature' + + ⁃ `denote-use-template' + + ⁃ `denote-use-title' + + When there exists a binding for the aforementioned variables, the + corresponding prompt is always skipped. It is thus paramount to never + set those variables outside the scope of a `let' (or equivalent). + + With those granted, here is another example scenario where the user + wants to have a constant value for the subdirectory but also be + prompted for a date. + + ┌──── + │ (with-eval-after-load 'org-capture + │ (add-to-list 'org-capture-templates + │ '("j" "New journal (with Denote)" plain + │ (file denote-last-path) + │ (function + │ (lambda () + │ ;; The "journal" subdirectory of the `denote-directory'---this must exist! + │ (let* ((denote-use-directory (expand-file-name "journal" (denote-directory))) + │ ;; Use the existing `denote-prompts' as well as the one for a date. + │ (denote-prompts (denote-add-prompts '(date)))) + │ (denote-org-capture)))) + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t))) + └──── + + The above highlights the hackability of the Denote code base, namely, + how we can affect the behaviour of the underlying `denote' command by + `let' binding variables that affect every aspect of its behaviour + ([Write your own convenience commands]). + + +[Create note using Org capture] See section 5.2 + +[Create note with specific prompts using Org capture] See section 5.3 + +[For developers or advanced users] See section 20 + +[Write your own convenience commands] See section 5.1.4.1 + + +5.5 Create a note with the region’s contents +──────────────────────────────────────────── + + The command `denote-region' takes the contents of the active region + and then calls the `denote' command. Once a new note is created, it + inserts the contents of the region therein. This is useful to quickly + elaborate on some snippet of text or capture it for future reference. + + When the `denote-region' command is called with an active region, it + finalises its work by calling + `denote-region-after-new-note-functions'. This is an abnormal hook, + meaning that the functions added to it are called with arguments. The + arguments are two, representing the beginning and end positions of the + newly inserted text. + + A common use-case for Org mode users is to call the command + `org-insert-structure-template' after a region is inserted. Emacs + will thus prompt for a structure template, such as the one + corresponding to a source block. In this case the function added to + `denote-region-after-new-note-functions' does not actually need + aforementioned arguments: it can simply declare those as ignored by + prefixing the argument names with an underscore (an underscore is + enough, but it is better to include a name for clarity). For example, + the following will prompt for a structure template as soon as + `denote-region' is done: + + ┌──── + │ (defun my-denote-region-org-structure-template (_beg _end) + │ (when (derived-mode-p 'org-mode) + │ (activate-mark) + │ (call-interactively 'org-insert-structure-template))) + │ + │ (add-hook 'denote-region-after-new-note-functions #'my-denote-region-org-structure-template) + └──── + + Remember that `denote-region-after-new-note-functions' are not called + if `denote-region' is used without an active region. + + +5.5.1 A custom `denote-region' that references the source +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The `denote-region' command simply creates a new note and includes the + highlighted region’s contents as the initial text of the note ([Create + a note with the region’s contents]). However, users may want a more + streamlined workflow where the command is always used to capture + quotes from other sources. In this example, we consider “other + sources” to come from Emacs EWW buffers (with `M-x eww') or regular + files outside the `denote-directory'. + + [ This is a proof-of-concept that does not cover all cases. If anyone + wants to use a variation of this, just let me know. ] + + ┌──── + │ ;; Variant of `my-denote-region' to reference the source + │ + │ (defun my-denote-region-get-source-reference () + │ "Get a reference to the source for use with `my-denote-region'. + │ The reference is a URL or an Org-formatted link to a file." + │ ;; We use a `cond' here because we can extend it to cover move + │ ;; cases. + │ (cond + │ ((derived-mode-p 'eww-mode) + │ (plist-get eww-data :url)) + │ ;; Here we are just assuming an Org format. We can make this more + │ ;; involved, if needed. + │ (buffer-file-name + │ (format "[[file:%s][%s]]" buffer-file-name (buffer-name))))) + │ + │ (defun my-denote-region () + │ "Like `denote-region', but add the context afterwards. + │ For how the context is retrieved, see `my-denote-region-get-source-reference'." + │ (interactive) + │ (let ((context (my-denote-region-get-source-reference))) + │ (call-interactively 'denote-region) + │ (when context + │ (goto-char (point-max)) + │ (insert "\n") + │ (insert context)))) + │ + │ ;; Add quotes around snippets of text captured with `denote-region' or `my-denote-region'. + │ + │ (defun my-denote-region-org-structure-template (beg end) + │ "Automatically quote (with Org syntax) the contents of `denote-region'." + │ (when (derived-mode-p 'org-mode) + │ (goto-char end) + │ (insert "#+end_quote\n") + │ (goto-char beg) + │ (insert "#+begin_quote\n"))) + │ + │ (add-hook 'denote-region-after-new-note-functions #'my-denote-region-org-structure-template) + └──── + + With the above in place, calling the `my-denote-region' command does + the following: + + • It creates a new note as usual, prompting for the relevant data. + • Inserts the contents of the region below the front matter of the new + note. + • Adds Org-style quotation block markers around the inserted region. + • Adds a link to the URL or file from where `my-denote-region' was + called. + + +[Create a note with the region’s contents] See section 5.5 + + +5.6 Open an existing note or create it if missing +───────────────────────────────────────────────── + + Sometimes it is necessary to briefly interrupt the ongoing writing + session to open an existing note or, if that is missing, to create it. + This happens when a new tangential thought occurs and the user wants + to confirm that an entry for it is in place. To this end, Denote + provides the command `denote-open-or-create' as well as its more + flexible counterpart `denote-open-or-create-with-command'. + + The `denote-open-or-create' prompts to visit a file in the + `denote-directory'. At this point, the user must type in search terms + that match a file name. If the input does not return any matches and + the user confirms their choice to proceed (usually by typing RET + twice, depending on the minibuffer settings), `denote-open-or-create' + will call the `denote' command interactively to create a new note. It + will then use whatever prompts `denote' normally has, per the user + option `denote-prompts' ([Standard note creation]). If the title + prompt is involved (the default behaviour), the + `denote-open-or-create' sets up this prompt to have the previous input + as the default title of the note to-be-created. This means that the + user can type RET at the empty prompt to re-use what they typed in + previously. Commands to use previous inputs from the history are also + available (`M-p' or `M-n' in the minibuffer, which call + `previous-history-element' and `next-history-element' by default). + Accessing the history is helpful to, for example, make further edits + to the available text. + + The `denote-open-or-create-with-command' is like the above, except + when it is about to create the new note it first prompts for the + specific file-creating command to use ([Points of entry]). For + example, the user may want to specify a signature for this new file, + so they can select the `denote-signature' command. + + Denote provides similar functionality for linking to an existing note + or creating a new one ([Link to a note or create it if missing]). + + +[Standard note creation] See section 5.1 + +[Points of entry] See section 5 + +[Link to a note or create it if missing] See section 9.10 + + +5.7 Maintain separate directory silos for notes +─────────────────────────────────────────────── + + The user option `denote-directory' accepts a value that represents the + path to a directory, such as `~/Documents/notes'. Normally, the user + will have one place where they store all their notes, in which case + this arrangement shall suffice. + + There is, however, the possibility to maintain separate directories of + notes. By “separate”, we mean that they do not communicate with each + other: no linking between them, no common keywords, nothing. Think of + the scenario where one set of notes is for private use and another is + for an employer. We call these separate directories “silos”. + + To create silos, the user must specify a local variable at the root of + the desired directory. This is done by creating a `.dir-locals.el' + file, with the following contents: + + ┌──── + │ ;;; Directory Local Variables. For more information evaluate: + │ ;;; + │ ;;; (info "(emacs) Directory Variables") + │ + │ ((nil . ((denote-directory . "/path/to/silo/")))) + └──── + + When inside the directory that contains this `.dir-locals.el' file, + all Denote commands/functions for note creation, linking, the + inference of available keywords, et cetera will use the silo as their + point of reference ([The `denote-silo' package which formerly was + `denote-silo-extras.el']). They will not read the global value of + `denote-directory'. The global value of `denote-directory' is read + everywhere else except the silos. + + In concrete terms, this is a representation of the directory + structures (notice the `.dir-locals.el' file is needed only for the + silos): + + ┌──── + │ ;; This is the global value of 'denote-directory' (no need for a .dir-locals.el) + │ ~/Documents/notes + │ |-- 20210303T120534--this-is-a-test__journal_philosophy.txt + │ |-- 20220303T120534--another-sample__journal_testing.md + │ `-- 20220620T181255--the-third-test__keyword.org + │ + │ ;; A silo with notes for the employer + │ ~/different/path/to/notes-for-employer + │ |-- .dir-locals.el + │ |-- 20210303T120534--this-is-a-test__conference.txt + │ |-- 20220303T120534--another-sample__meeting.md + │ `-- 20220620T181255--the-third-test__keyword.org + │ + │ ;; Another silo with notes for my volunteering + │ ~/different/path/to/notes-for-volunteering + │ |-- .dir-locals.el + │ |-- 20210303T120534--this-is-a-test__activism.txt + │ |-- 20220303T120534--another-sample__teambuilding.md + │ `-- 20220620T181255--the-third-test__keyword.org + └──── + + It is possible to configure other user options of Denote to have a + silo-specific value. For example, this one changes the + `denote-known-keywords' only for this particular silo: + + ┌──── + │ ;;; Directory Local Variables. For more information evaluate: + │ ;;; + │ ;;; (info "(emacs) Directory Variables") + │ + │ ((nil . ((denote-directory . "/path/to/silo/") + │ (denote-known-keywords . ("food" "drink"))))) + └──── + + This one is like the above, but also disables `denote-infer-keywords': + + ┌──── + │ ;;; Directory Local Variables. For more information evaluate: + │ ;;; + │ ;;; (info "(emacs) Directory Variables") + │ + │ ((nil . ((denote-directory . "/path/to/silo/") + │ (denote-known-keywords . ("food" "drink")) + │ (denote-infer-keywords . nil)))) + └──── + + To expand the list of local variables to, say, cover specific major + modes, we can do something like this: + + ┌──── + │ ;;; Directory Local Variables. For more information evaluate: + │ ;;; + │ ;;; (info "(emacs) Directory Variables") + │ + │ ((nil . ((denote-directory . "/path/to/silo/") + │ (denote-known-keywords . ("food" "drink")) + │ (denote-infer-keywords . nil))) + │ (org-mode . ((org-hide-emphasis-markers . t) + │ (org-hide-macro-markers . t) + │ (org-hide-leading-stars . t)))) + └──── + + As not all user options have a “safe” local value, Emacs will ask the + user to confirm their choice and to store it in the Custom code + snippet that is normally appended to init file (or added to the file + specified by the user option `custom-file'). + + Finally, it is possible to have a `.dir-locals.el' for subdirectories + of any `denote-directory'. Perhaps to specify a different set of + known keywords, while not making the subdirectory a silo in its own + right. We shall not expand on such an example, as we trust the user + to experiment with the best setup for their workflow. + + Feel welcome to ask for help if the information provided herein is not + sufficient. The manual shall be expanded accordingly. + + +[The `denote-silo' package which formerly was `denote-silo-extras.el'] +See section 18.5 + +5.7.1 Make Org export work with silos +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The Org export infrastructure is designed to ignore directory-local + variables. This means that Denote silos, which depend on setting the + local value of the variable `denote-directory', do not work as + intended ([Maintain separate directory silos for notes]). More + specifically, the Denote links do not resolve to the right file, + because their path is changed during the export process. + + I brought this to the attention of the Org maintainer. The guidance + from their side is to use the `#+bind' keyword to specify a local + value for the `denote-directory': + . + The prerequisite is to set `org-export-allow-bind-keywords' to a + non-nil value: + + ┌──── + │ (setq org-export-allow-bind-keywords t) + └──── + + I do not think this is an elegant solution, but here are two possible + ways to go about it, anyway: + + 1. Manually add the `#+bind' keyword to each file you want to export. + It has to be like this: + + ┌──── + │ #+bind: denote-directory "/path/to/silo/" + └──── + + 2. Alternatively, you can make the Org front matter that Denote uses + for new files automatically include the `#+bind' keyword with its + desired value. Here is a complete `.dir-locals.el' which (i) + defines the silo and (ii) modifies the `denote-org-front-matter' + accordingly: + + ┌──── + │ ;;; Directory Local Variables. For more information evaluate: + │ ;;; + │ ;;; (info "(emacs) Directory Variables") + │ + │ ((nil . ((denote-directory . "/path/to/silo/") + │ (denote-org-front-matter . + │ "#+title: %s + │ #+date: %s + │ #+filetags: %s + │ #+identifier: %s + │ #+bind: denote-directory \"/path/to/silo/\" + │ \n")))) + └──── + + [ Note that if you are reading the Org source of this manual, you + need to use the command `org-edit-special' on the above code + blocks before copying the code. This is because Org automatically + prepends a comma to disambiguate those entries from actual + keywords of the current file. ] + + +[Maintain separate directory silos for notes] See section 5.7 + + +5.8 Exclude certain files from file prompts +─────────────────────────────────────────── + + The user option `denote-excluded-files-regexp' is a regular expression + that matches files names which should be excluded from all Denote file + prompts. Such prompts are present when linking to a file with one of + the many commands, like `denote-link' ([Linking notes]), or when + trying to open a file that may or may not exist ([Open an existing + note or create it if missing]). + + Functions that check for files include `denote-directory-files' and + `denote-file-prompt'. + + The match is performed with `string-match-p'. + + [For developers or advanced users]. + + +[Linking notes] See section 9 + +[Open an existing note or create it if missing] See section 5.6 + +[For developers or advanced users] See section 20 + + +5.9 Exclude certain directories from all operations +─────────────────────────────────────────────────── + + The user option `denote-excluded-directories-regexp' instructs all + Denote functions that read or check file/directory names to omit + directories that match the given regular expression. The regexp needs + to match only the name of the directory, not its full path. + + Affected operations include file prompts and functions that return the + available files in the value of the user option `denote-directory' + ([Maintain separate directory silos for notes]). + + File prompts are used by several commands, such as `denote-link' and + `denote-subdirectory'. + + Functions that check for files include `denote-directory-files' and + `denote-directory-subdirectories'. + + The match is performed with `string-match-p'. + + [For developers or advanced users]. + + +[Maintain separate directory silos for notes] See section 5.7 + +[For developers or advanced users] See section 20 + + +5.10 Exclude certain keywords from being inferred +───────────────────────────────────────────────── + + The user option `denote-excluded-keywords-regexp' omits keywords that + match a regular expression from the list of inferred keywords. + + Keywords are inferred from file names and provided at relevant prompts + as completion candidates when the user option `denote-infer-keywords' + is non-nil. + + The match is performed with `string-match-p'. + + +5.11 Create a controlled vocabulary for keywords +──────────────────────────────────────────────── + + Denote has two ways to know about keywords: the predefined list of + strings specified in the user option `denote-known-keywords' as well + as all the keywords it finds in the files of the `denote-directory' + when the user option `denote-infer-keywords' is set to a non-nil value + ([Exclude certain keywords from being inferred]). + + While this is a viable setup, users may prefer to implement a + “controlled vocabulary”. This is a predefined set of keywords whose + purpose is to avoid the creation of overly specific or inconsistent + keywords. + + To establish such a controlled vocabulary, users need only have + something like this in their configuration: + + ┌──── + │ ;; Do not read keywords from files. The only source is the `denote-known-keywords'. + │ (setq denote-infer-keywords nil) + │ + │ ;; Define the list of keywords. Each keyword is a string. + │ (setq denote-known-keywords (list "politics" "economics" "emacs" "philosophy")) + └──── + + +[Exclude certain keywords from being inferred] See section 5.10 + + +5.12 Use Denote commands from the menu bar or context menu +────────────────────────────────────────────────────────── + + Denote registers a submenu for the `menu-bar-mode'. Users will find + the entry called “Denote”. From there they can use their pointer to + select a command. For a sample of how this looks, read the + development log: + . + + The command `denote-menu-bar-mode' toggles the presentation of the + menu. It is enabled by default. + + Emacs also provides support for operations through a context menu. + This is typically the set of actions that are made available via a + right mouse click. Users who enable `context-menu-mode' can register + the Denote entry for it by adding the following to their configuration + file: + + ┌──── + │ (add-hook 'context-menu-functions #'denote-context-menu) + └──── + + +6 Renaming files +════════════════ + + Denote provides commands to rename files and update their front matter + where relevant. For Denote to work, only the file name needs to be in + order, by following our naming conventions ([The file-naming scheme]). + The linking mechanism, in particular, needs just the identifier in the + file name ([Linking notes]). + + We write front matter in notes for the user’s convenience and for + other tools to make use of that information (e.g. Org’s export + mechanism). The renaming mechanism takes care to keep this data in + sync with the file name, when the user performs a change. + + Renaming is useful for managing existing files created with Denote, + but also for converting older text files to Denote notes. Denote’s + file-naming scheme is not specific to notes or text files: it is + relevant for all sorts of items, such as multimedia and PDFs that form + part of the user’s longer-term storage. While Denote does not manage + such files (e.g. doesn’t create links to them), it already has all the + mechanisms to facilitate the task of renaming them. + + All renaming commands run the `denote-after-rename-file-hook' after a + succesful operation ([Access the data of the latest note]). They also + construct the file name in accordance with the user option + `denote-file-name-components-order' ([Change the order of file name + components]). + + Apart from renaming files, Denote can also rename only the buffer. + The idea is that the underlying file name is correct but it can be + easier to use shorter buffer names when displaying them on the mode + line or switching between then with commands like `switch-to-buffer'. + + [Automatically rename Denote buffers]. + + [Find duplicate identifiers and put them in a Dired buffer]. + + +[The file-naming scheme] See section 7 + +[Linking notes] See section 9 + +[Access the data of the latest note] See section 19.1 + +[Change the order of file name components] See section 7.1 + +[Automatically rename Denote buffers] See section 12 + +[Find duplicate identifiers and put them in a Dired buffer] See section +6.10 + +6.1 Rename a single file +──────────────────────── + + The `denote-rename-file' command renames a file and updates existing + front matter if appropriate. It is possible to do the same with + multiple files ([Rename multiple files interactively]). + + It always renames the file where it is located in the file system: it + never moves it to another directory. + + If in Dired, it considers `FILE' to be the one at point, else it + prompts with minibuffer completion for one. When called from Lisp, + `FILE' is a file system path represented as a string. + + If `FILE' has a Denote-compliant identifier, it retains it while + updating components of the file name referenced by the user option + `denote-prompts' ([The `denote-prompts' option]). By default, these + are the `TITLE' and `KEYWORDS'. The `SIGNATURE' is another one. When + called from Lisp, `TITLE' and `SIGNATURE' are strings, while + `KEYWORDS' is a list of strings. + + If there is no identifier, `denote-rename-file' creates an identifier + based on the following conditions: + + 1. If the `denote-prompts' includes an entry for date prompts, then it + prompts for `DATE' and takes its input to produce a new + identifier. For use in Lisp, `DATE' must conform with + `denote-valid-date-p'. + + 2. If `DATE' is nil (e.g. when `denote-prompts' does not include a + date entry), it uses the file attributes to determine the last + modified date of `FILE' and formats it as an identifier. + + 3. As a fallback, it derives an identifier from the current date and + time. + + 4. At any rate, if the resulting identifier is not unique among the + files in the variable `denote-directory', it increments it such + that it becomes unique. + + In interactive use, and assuming `denote-prompts' includes a title + entry, the `denote-rename-file' makes the `TITLE' prompt have + prefilled text in the minibuffer that consists of the current title of + `FILE'. The current title is either retrieved from the front matter + (such as the `#+title' in Org) or from the file name. + + The command does the same for the `SIGNATURE' prompt, subject to + `denote-prompts', by prefilling the minibuffer with the current + signature of `FILE', if any. + + Same principle for the `KEYWORDS' prompt: it converts the keywords in + the file name into a comma-separated string and prefills the + minibuffer with it (the `KEYWORDS' prompt accepts more than one + keywords, each separated by a comma, else the `crm-separator'). + + For all prompts, the `denote-rename-file' interprets an empty input as + an instruction to remove that file name component. For example, if a + `TITLE' prompt is available and `FILE' is + `20240211T093531--some-title__keyword1.org' then it renames `FILE' to + `20240211T093531__keyword1.org'. + + In interactive use, if there is no entry for a file name component in + `denote-prompts', keep it as-is ([The `denote-prompts' option]). + + When called from Lisp, the special symbol `keep-current’ can be used + for the TITLE, KEYWORDS, SIGNATURE and DATE parameters to keep them + as-is. + + [ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + `vertico' use the first available completion candidate instead. For + `vertico', the user must either move one up to select the prompt and + then type `RET' there with empty contents, or use the command + `vertico-exit-input' with empty contents. That Vertico command is + bound to `M-RET' as of this writing on 2024-02-13 08:08 +0200. ] + + When renaming `FILE', the command reads its file type extension (like + `.org') and preserves it through the renaming process. Files that have + no extension are left without one. + + As a final step, ask for confirmation, showing the difference between + old and new file names. Do not ask for confirmation if the user + option `denote-rename-confirmations' does not contain the symbol + `modify-file-name' ([The `denote-rename-confirmations' option]). + + If `FILE' has front matter for `TITLE' and `KEYWORDS', ask to rewrite + their values in order to reflect the new input, unless + `denote-rename-confirmations' lacks `rewrite-front-matter'. When the + `denote-save-buffers' is nil (the default), do not save the underlying + buffer, thus giving the user the option to double-check the result, + such as by invoking the command `diff-buffer-with-file'. The rewrite + of the `TITLE' and `KEYWORDS' in the front matter should not affect + the rest of the front matter. + + If the file does not have front matter but is among the supported file + types (per `denote-file-type'), add front matter to the top of it and + leave the buffer unsaved for further inspection ([Front matter]). Save + the buffer if `denote-save-buffers' is non-nil ([The + `denote-save-buffers' option]). + + Construct the file name in accordance with the user option + `denote-file-name-components-order' ([Change the order of file name + components]). + + Run the `denote-after-rename-file-hook' after renaming `FILE' ([Access + the data of the latest note]). + + This command is intended to (i) rename Denote files, (ii) convert + existing supported file types to Denote notes, and (ii) rename + non-note files (e.g. `PDF') that can benefit from Denote’s file-naming + scheme. + + For a version of this command that works with multiple files + one-by-one, use `denote-dired-rename-files' ([Rename multiple files + interactively]). + + +[Rename multiple files interactively] See section 6.3 + +[The `denote-prompts' option] See section 5.1.1 + +[The `denote-rename-confirmations' option] See section 6.1.1 + +[Front matter] See section 8 + +[The `denote-save-buffers' option] See section 5.1.5 + +[Change the order of file name components] See section 7.1 + +[Access the data of the latest note] See section 19.1 + +6.1.1 The `denote-rename-confirmations' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-rename-confirmations' controls what kind of + confirmation renaming commands ask for ([Renaming files]). Its value + is a list of symbols. + + The value is either nil, in which case no confirmation is ever + requested, or a list of symbols among the following: + + • `modify-file-name' means that renaming commands will ask for + confirmation before modifying the file name. + + • `rewrite-front-matter' means that renaming commands will ask for + confirmation before rewritting the front matter. + + • `add-front-matter' means that renaming commands will ask for + confirmation before adding new front matter to the file. + + The default behaviour of the `denote-rename-file' command (and others + like it) is to ask for an affirmative answer as a final step before + changing the file name and, where relevant, inserting or updating the + corresponding front matter. + + Specialized commands that build on top of `denote-rename-file' (or + related) may internally bind this user option to a non-nil value in + order to perform their operation (e.g. `denote-dired-rename-files' + goes through each marked Dired file, prompting for the information to + use, but carries out the renaming without asking for confirmation + ([Rename multiple files interactively])). + + +[Renaming files] See section 6 + +[Rename multiple files interactively] See section 6.3 + + +6.2 Rename a single file based on its front matter +────────────────────────────────────────────────── + + In the previous section, we covered the more general mechanism of the + command `denote-rename-file' ([Rename a single file]). There is also + a way to have the same outcome by making Denote read the data in the + current file’s front matter and use it to construct/update the file + name. The command for this is + `denote-rename-file-using-front-matter'. It is only relevant for + files that (i) are among the supported file types, per + `denote-file-type', and (ii) have the requisite front matter in place. + + Suppose you have an `.org' file with this front matter ([Front + matter]): + + ┌──── + │ #+title: My sample note file + │ #+date: [2022-08-05 Fri 13:10] + │ #+filetags: :testing: + │ #+identifier: 20220805T131044 + └──── + + Its file name reflects this information: + + ┌──── + │ 20220805T131044--my-sample-note-file__testing.org + └──── + + + You want to change its title and keywords manually, so you modify it + thus: + + ┌──── + │ #+title: My modified sample note file + │ #+date: [2022-08-05 Fri 13:10] + │ #+filetags: :testing:denote:emacs: + │ #+identifier: 20220805T131044 + └──── + + At this stage, the file name still shows the old title and keywords. + You now invoke `denote-rename-file-using-front-matter' and it updates + the file name to: + + ┌──── + │ 20220805T131044--my-modified-sample-note-file__testing_denote_emacs.org + └──── + + + By default, the renaming is subject to a “yes or no” prompt that shows + the old and new names, just so the user is certain about the change. + Though this can be modified ([The `denote-rename-confirmations' + option]). + + The identifier of the file, if any, is never modified even if it is + edited in the front matter: Denote considers the file name to be the + source of truth in this case, to avoid potential breakage with typos + and the like. + + This command constructs the file name in accordance with the user + option `denote-file-name-components-order' ([Change the order of file + name components]). + + +[Rename a single file] See section 6.1 + +[Front matter] See section 8 + +[The `denote-rename-confirmations' option] See section 6.1.1 + +[Change the order of file name components] See section 7.1 + + +6.3 Rename multiple files interactively +─────────────────────────────────────── + + The command `denote-dired-rename-files' (alias + `denote-dired-rename-marked-files') renames the files that are marked + in a Dired buffer. Its behaviour is similar to the + `denote-rename-file' in that it prompts for a title, keywords, and + signature ([Rename a single file]). It does so over each marked file, + renaming one after the other. + + Unlike `denote-rename-file', the command `denote-dired-rename-files' + does not ask to confirm the changes made to the files: it performs + them outright (same as setting `denote-rename-confirmations' to a nil + value). This is done to make it easier to rename multiple files + without having to confirm each step. For an even more direct approach, + check the command `denote-dired-rename-marked-files-with-keywords'. + + • [Rename by writing only keywords] + • [Rename multiple files based on their front matter] + + +[Rename a single file] See section 6.1 + +[Rename by writing only keywords] See section 6.4 + +[Rename multiple files based on their front matter] See section 6.5 + + +6.4 Rename multiple files at once by asking only for keywords +───────────────────────────────────────────────────────────── + + The `denote-dired-rename-marked-files-with-keywords' command renames + marked files in Dired to conform with our file-naming scheme. It does + so by writing keywords to them. Specifically, it does the following: + + • retains the file’s existing name and makes it the `TITLE' field, per + Denote’s file-naming scheme; + + • sluggifies the `TITLE' and adjusts its letter casing, according to + our conventions; + + • prepends an identifier to the `TITLE', if one is missing; + + • preserves the file’s extension, if any; + + • prompts once for `KEYWORDS' and applies the user’s input to the + corresponding field in the file name, rewriting any keywords that + may exist while removing keywords that do exist if `KEYWORDS' is + empty; + + • adds or rewrites existing front matter to the underlying file, if it + is recognized as a Denote note (per the `denote-file-type' user + option), such that it includes the new keywords. + + [ Note that the affected buffers are not saved, unless the user option + `denote-rename-no-confirm' is non-nil. Users can thus check them to + confirm that the new front matter does not cause any problems (e.g. + with the `diff-buffer-with-file' command). Multiple buffers can be + saved in one go with the command `save-some-buffers' (read its doc + string). ] + + Construct the file name in accordance with the user option + `denote-file-name-components-order' ([Change the order of file name + components]). + + Run the `denote-after-rename-file-hook' after the renaming is done. + + For more specialized versions of this command that only add or remove + keywords, use `denote-dired-rename-marked-files-add-keywords' and + `denote-dired-rename-marked-files-remove-keywords', respectively. + + +[Change the order of file name components] See section 7.1 + + +6.5 Rename multiple files based on their front matter +───────────────────────────────────────────────────── + + As already noted, Denote can rename a file based on the data in its + front matter ([Rename a single file based on its front matter]). The + command `denote-dired-rename-marked-files-using-front-matter' extends + this principle to a batch operation which applies to all marked files + in Dired. + + Marked files must count as notes for the purposes of Denote, which + means that they at least have an identifier in their file name and use + a supported file type, per `denote-file-type'. Files that do not meet + this criterion are ignored, because Denote cannot know if they have + front matter and what that may be. For such files, it is still + possible to rename them interactively ([Rename multiple files + interactively]). + + +[Rename a single file based on its front matter] See section 6.2 + +[Rename multiple files interactively] See section 6.3 + + +6.6 Rename a file by changing only its file type +──────────────────────────────────────────────── + + The command `denote-change-file-type-and-front-matter' provides the + convenience of converting a note taken in one file type, say, `.txt' + into another like `.org'. It presents a choice among the + `denote-file-type' options. + + The conversion does NOT modify the existing front matter. Instead, it + prepends new front matter to the top of the file. We do this as a + safety precaution since the user can, in principle, add arbitrary + extras to their front matter that we would not want to touch. + + If in Dired, `denote-change-file-type-and-front-matter' operates on + the file at point, else the current file, else it prompts with + minibuffer completion for one. + + The title of the file is retrieved from a line starting with a title + field in the file’s front matter, depending on the previous file type + (e.g. `#+title' for Org). The same process applies for keywords. + + As a final step, the command asks for confirmation, showing the + difference between old and new file names. + + This command constructs the file name in accordance with the user + option `denote-file-name-components-order' ([Change the order of file + name components]). + + +[Change the order of file name components] See section 7.1 + + +6.7 Rename a file by adding or removing a title interactively +───────────────────────────────────────────────────────────── + + The command `denote-rename-file-title' streamlines the process of + interactively adding or removing a title to/from a file, while + changing its file name accordingly. It asks for a title using the + familiar minibuffer prompt ([Standard note creation]). It then renames + the file. The command respect the values of + `denote-rename-confirmations' and `denote-save-buffers': + + • [The `denote-rename-confirmations' option]. + • [The `denote-save-buffers' option]. + + Technically, `denote-rename-file-title' is a wrapper for + `denote-rename-file', doing all the things that does ([Rename a single + file]). + + Concretely, this command can add or remove a title in one go. It does + it by prepopulating the minibuffer prompt with the existing + title. Users can then modify it. An empty input means to remove the + title altogether ([The file-naming scheme]). + + [ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + `vertico' use the first available completion candidate instead. For + `vertico', the user must either move one up to select the prompt and + then type `RET' there with empty contents, or use the command + `vertico-exit-input' with empty contents. That Vertico command is + bound to `M-RET' as of this writing on 2024-06-30 10:37 +0300. ] + + +[Standard note creation] See section 5.1 + +[The `denote-rename-confirmations' option] See section 6.1.1 + +[The `denote-save-buffers' option] See section 5.1.5 + +[Rename a single file] See section 6.1 + +[The file-naming scheme] See section 7 + + +6.8 Rename a file by adding or removing keywords interactively +────────────────────────────────────────────────────────────── + + The command `denote-rename-file-keywords' streamlines the process of + interactively adding or removing keywords to a file, while changing + its file name and front matter accordingly. It asks for keywords using + the familiar minibuffer prompt ([Standard note creation]). It then + renames the file ([Rename a single file based on its front matter]). + The command respect the values of `denote-rename-confirmations' and + `denote-save-buffers': + + • [The `denote-rename-confirmations' option]. + • [The `denote-save-buffers' option]. + + Technically, `denote-rename-file-keywords' is a wrapper for + `denote-rename-file', doing all the things that does ([Rename a single + file]). + + Concretely, this command can add or remove keywords in one go. It does + it by prepopulating the minibuffer prompt with the existing keywords. + Users can then use the `crm-separator' (normally a comma), to write + new keywords or edit what is in the prompt to rewrite them + accordingly. An empty input means to remove all keywords ([The + file-naming scheme]). + + [ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + `vertico' use the first available completion candidate instead. For + `vertico', the user must either move one up to select the prompt and + then type `RET' there with empty contents, or use the command + `vertico-exit-input' with empty contents. That Vertico command is + bound to `M-RET' as of this writing on 2024-06-30 10:37 +0300. ] + + +[Standard note creation] See section 5.1 + +[Rename a single file based on its front matter] See section 6.2 + +[The `denote-rename-confirmations' option] See section 6.1.1 + +[The `denote-save-buffers' option] See section 5.1.5 + +[Rename a single file] See section 6.1 + +[The file-naming scheme] See section 7 + + +6.9 Rename a file by adding or removing a signature interactively +───────────────────────────────────────────────────────────────── + + The command `denote-rename-file-signature' streamlines the process of + interactively adding or removing a signature to/from a file, while + changing its file name accordingly. It asks for a signature using the + familiar minibuffer prompt ([Standard note creation]). It then renames + the file. The command respect the values of + `denote-rename-confirmations' and `denote-save-buffers': + + • [The `denote-rename-confirmations' option]. + • [The `denote-save-buffers' option]. + + Technically, `denote-rename-file-signature' is a wrapper for + `denote-rename-file', doing all the things that does ([Rename a single + file]). + + Concretely, this command can add or remove a signature in one go. It + does it by prepopulating the minibuffer prompt with the existing + signature. Users can then modify it. An empty input means to remove + the signature altogether ([The file-naming scheme]). + + [ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + `vertico' use the first available completion candidate instead. For + `vertico', the user must either move one up to select the prompt and + then type `RET' there with empty contents, or use the command + `vertico-exit-input' with empty contents. That Vertico command is + bound to `M-RET' as of this writing on 2024-06-30 10:37 +0300. ] + + +[Standard note creation] See section 5.1 + +[The `denote-rename-confirmations' option] See section 6.1.1 + +[The `denote-save-buffers' option] See section 5.1.5 + +[Rename a single file] See section 6.1 + +[The file-naming scheme] See section 7 + + +6.10 Find duplicate identifiers and put them in a Dired buffer +────────────────────────────────────────────────────────────── + + Denote takes care to create unique identifiers, though its mechanism + relies on reading the existing identifiers in the `denote-directory' + or the current directory. When we are renaming files across different + directories, there is a small chance that some files have the same + attributes and are thus assigned identical identifiers. If those files + ever make it into a consolidated `denote-directory', we will have + duplicates, which break the linking mechanism. + + As this is an edge case, we do not include any code to address it in + the Denote code base. Though here is a way to find duplicate + identifiers inside the current directory: + + ┌──── + │ (defun my-denote--get-files-in-dir (directory) + │ "Return file names in DIRECTORY." + │ (directory-files directory :full-paths directory-files-no-dot-files-regexp)) + │ + │ (defun my-denote--same-identifier-p (file1 file2) + │ "Return non-nil if FILE1 and FILE2 have the same identifier." + │ (let ((id1 (denote-retrieve-filename-identifier file1)) + │ (id2 (denote-retrieve-filename-identifier file2))) + │ (equal id1 id2))) + │ + │ (defun my-denote-find-duplicate-identifiers (directory) + │ "Find all files in DIRECTORY that need a new identifier." + │ (let* ((ids (my-denote--get-files-in-dir directory)) + │ (unique-ids (seq-uniq ids #'my-denote--same-identifier-p))) + │ (seq-difference ids unique-ids #'equal))) + │ + │ (defun my-denote-dired-show-duplicate-identifiers (directory) + │ "Put duplicate identifiers from DIRECTORY in a dedicated Dired buffer." + │ (interactive + │ (list + │ (read-directory-name "Select DIRECTORY to check for duplicate identifiers: " default-directory))) + │ (if-let* ((duplicates (my-denote-find-duplicate-identifiers directory))) + │ (dired (cons (format "Denote duplicate identifiers" directory) duplicates)) + │ (message "No duplicates identifiers in `%s'" directory))) + └──── + + Evaluate this code and then call the command + `my-denote-dired-show-duplicate-identifiers'. If there are + duplicates, it will put them in a dedicated Dired buffer. From there, + you can view the file contents as usual, and manually edit the + identifiers as you see fit (e.g. edit them one by one, or change to + the writable Dired and record a keyboard macro that makes use of a + counter to increment by 1—contact me if you need any help). + + +6.11 Faces used by rename commands +────────────────────────────────── + + These are the faces used by the various Denote rename commands to + style or highlight the old/new/current file shown in the relevant + minibuffer prompts: + + • `denote-faces-prompt-current-name' + • `denote-faces-prompt-new-name' + • `denote-faces-prompt-old-name' + + +7 The file-naming scheme +════════════════════════ + + Notes are stored in the `denote-directory'. The default path is + `~/Documents/notes'. The `denote-directory' can be a flat listing, + meaning that it has no subdirectories, or it can be a directory tree. + Either way, Denote takes care to only consider “notes” as valid + candidates in the relevant operations and will omit other files or + directories. + + Every note produced by Denote follows this pattern by default ([Points + of entry]): + + ┌──── + │ DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION + └──── + + + The `DATE' field represents the date in year-month-day format followed + by the capital letter `T' (for “time”) and the current time in + hour-minute-second notation. The presentation is compact: + `20220531T091625'. The `DATE' serves as the unique identifier of each + note and, as such, is also known as the file’s ID or identifier. + + File names can include an arbitrary string of alphanumeric characters + in the `SIGNATURE' field. Signatures have no clearly defined purpose + and are up to the user to define. They can serve as special labels, + such as `part1' and `part2' of a large file, or as priority indicators + like `a', `b', `c', or even context/scope specifiers like `home' and + `work'. Another use-case is to write sequences of thoughts, such that + notes form a hierarchy, something we support with the optional and + comprehensive extension `denote-sequence.el' ([Write sequence notes or + “folgezettel”]). Signatures are an optional extension to Denote’s + file-naming scheme. In the simplest form, they can be added to newly + created files on demand, with the command `denote-signature', or by + modifying the value of the user option `denote-prompts' ([The + `denote-prompts' option]). + + The `TITLE' field is the title of the note, as provided by the user. + It automatically gets downcased by default and is also hyphenated + ([Sluggification of file name components]). An entry about “Economics + in the Euro Area” produces an `economics-in-the-euro-area' string for + the `TITLE' of the file name. + + The `KEYWORDS' field consists of one or more entries demarcated by an + underscore (the separator is inserted automatically). Each keyword is + a string provided by the user at the relevant prompt which broadly + describes the contents of the entry. + + Each of the keywords is a single word, with multiple keywords + providing the multi-dimensionality needed for advanced searches + through Denote files. Users who need to compose a keyword out of + multiple words such as camelCase/CamelCase and are encouraged to use + the `denote-file-name-slug-functions' user option accordingly + ([Sluggification of file name components]). + + The `EXTENSION' is the file type. By default, it is `.org' + (`org-mode') though the user option `denote-file-type' provides + support for Markdown with YAML or TOML variants (`.md' which runs + `markdown-mode') and plain text (`.txt' via `text-mode'). Consult its + doc string for the minutiae. While files end in the `.org' extension + by default, the Denote code base does not actually depend on org.el + and/or its accoutrements. + + Examples: + + ┌──── + │ 20220610T043241--initial-thoughts-on-the-zettelkasten-method__notetaking.org + │ 20220610T062201--define-custom-org-hyperlink-type__denote_emacs_package.md + │ 20220610T162327--on-hierarchy-and-taxis__notetaking_philosophy.txt + └──── + + + The different field separators, namely `--' and `__' introduce an + efficient way to anchor searches (such as with Emacs commands like + `isearch' or from the command-line with `find' and related). A query + for `_word' always matches a keyword, while a regexp in the form of, + say, `"\\([0-9T]+?\\)--\\(.*?\\)_"' captures the date in group `\1' + and the title in `\2' (test any regular expression in the current + buffer by invoking `M-x re-builder'). + + [Features of the file-naming scheme for searching or filtering]. + + The `denote-prompts' can be configured in such ways to yield the + following file name permutations: + + ┌──── + │ DATE.EXT + │ DATE--TITLE.EXT + │ DATE__KEYWORDS.EXT + │ DATE==SIGNATURE.EXT + │ DATE==SIGNATURE--TITLE.EXT + │ DATE==SIGNATURE--TITLE__KEYWORDS.EXT + │ DATE==SIGNATURE__KEYWORDS.EXT + └──── + + + When in doubt, stick to the default design, which is carefully + considered and works well ([Change the order of file name + components]). + + While Denote is an Emacs package, notes should work long-term and not + depend on the functionality of a specific program. The file-naming + scheme we apply guarantees that a listing is readable in a variety of + contexts. The Denote file-naming scheme is, in essence, an effective, + low-tech invention. + + +[Points of entry] See section 5 + +[Write sequence notes or “folgezettel”] See section 18.2 + +[The `denote-prompts' option] See section 5.1.1 + +[Sluggification of file name components] See section 7.2 + +[Features of the file-naming scheme for searching or filtering] See +section 7.4 + +[Change the order of file name components] See section 7.1 + +7.1 Change the order of file name components +──────────────────────────────────────────── + + Our standard file-naming scheme prescribes a specific order for the + file name components ([The file-naming scheme]). Though we provide the + user option `denote-file-name-components-order' to let the user + reorder them as they see fit. + + The value of this user option is a list of the following symbols: + + • `identifier': This is the combination of the date and time. When it + is the first on the list, it looks like `20240519T073456' and does + not have a component separator of its own due its unambiguous + format. When it is placed anywhere else in the file name, it is + prefixed with `@@', so it looks like `@@20240519T073456'. + + • `signature': This is an arbitrary string that can be used to qualify + the file in some way, according to the user’s methodology (e.g. to + add a sequence to notes). The string is always prefixed with the + `==' to remain unambiguous. + + • `title': This is an arbitrary string which describes the file. It is + always prefixed with `--' to be unambiguous. + + • `keywords': This is a series of one or more words that succinctly + group the file. Multiple keywords are separated by an underscore + prefixed to each of them. The file name component is always prefixed + with `__'. + + All four symbols must appear exactly once. Duplicates are ignored. Any + missing symbol is added automatically. + + Some examples: + + ┌──── + │ (setq denote-file-name-components-order '(identifier signature title keywords)) + │ ;; => 20240519T07345==hello--this-is-the-title__denote_testing.org + │ + │ (setq denote-file-name-components-order '(signature identifier title keywords)) + │ ;; => ==hello@@20240519T07345--this-is-the-title__denote_testing.org + │ + │ (setq denote-file-name-components-order '(title signature identifier keywords)) + │ ;; => --this-is-the-title==hello@@20240519T07345__denote_testing.org + │ + │ (setq denote-file-name-components-order '(keywords title signature identifier)) + │ ;; => __denote_testing--this-is-the-title==hello@@20240519T07345.org + └──── + + Also see how to configure the Denote prompts, which affect which + components are actually used in the order specified herein ([The + `denote-prompts' option]). + + Before deciding on this, please consider the longer-term implications + of file names with varying patterns. Consistency makes things + predictable and thus easier to find. So pick one order and never touch + it again. When in doubt, leave the default file-naming scheme as-is. + + +[The file-naming scheme] See section 7 + +[The `denote-prompts' option] See section 5.1.1 + + +7.2 Sluggification of file name components +────────────────────────────────────────── + + Files names can contain any character that the file system + permits. Denote imposes a few additional restrictions: + + ⁃ The tokens “`=", =__' and `--' are interpreted by Denote and should + appear only once. + + ⁃ The dot character is not allowed in a note’s file name, except to + indicate the file type extension. Denote recognises two extensions + for encrypted files, like `.txt.gpg'. + + By default, Denote enforces other rules to file names through the user + option `denote-file-name-slug-functions'. These rules are applied to + file names by default: + + ⁃ What we count as “illegal characters” are removed. + + ⁃ Input for a file title is hyphenated. The original value is + preserved in the note’s contents ([Front matter]). + + ⁃ Spaces or other delimiters are removed from keywords, meaning that + `hello-world' becomes `helloworld'. This is because hyphens in + keywords do not work everywhere, such as in Org. Plus, hyphens are + word separators in the title and we want to keep distinct separators + for each component to make search easier and semantic ([Features of + the file-naming scheme for searching or filtering]). + + ⁃ Signatures are like the above, but use the equals sign instead of + hyphens as a word separator. + + ⁃ All file name components are downcased. Further down we document how + to deviate from these rules, such as to accept input of the form + `helloWorld' or `HelloWorld' verbatim. + + Denote imposes these restrictions to enforce uniformity, which is + helpful long-term as it keeps all files with the same predictable + pattern. Too many permutations make searches more difficult to express + accurately and be confident that the matches cover all files. + Nevertheless, one of the principles of Denote is its flexibility or + hackability and so users can deviate from the aforementioned + ([User-defined sluggification of file name components]). + + +[Front matter] See section 8 + +[Features of the file-naming scheme for searching or filtering] See +section 7.4 + +[User-defined sluggification of file name components] See section 7.3 + + +7.3 User-defined sluggification of file name components +─────────────────────────────────────────────────────── + + The user option `denote-file-name-slug-functions' controls the + sluggification of file name components ([Sluggification of file name + components]). The default method is outlined above and in the + previous section ([The file-naming scheme]). + + The value of this user option is an alist where each element is a cons + cell of the form `(COMPONENT . METHOD)'. For example, here is the + default value: + + ┌──── + │ '((title . denote-sluggify-title) + │ (signature . denote-sluggify-signature) + │ (keyword . denote-sluggify-keyword)) + └──── + + • The `COMPONENT' is an unquoted symbol among `title', `signature', + `keyword', which refers to the corresponding component of the file + name. + + • The `METHOD' is a function to format the given component. This + function must take a string as its parameter and return the string + formatted for the file name. Note that even in the case of the + `keyword' component, the function receives one string representing a + single keyword and returns it formatted for the file name. Joining + the keywords together is handled internally by Denote. + + One commonly requested deviation from the sluggification rules is to + not sluggify individual keywords, such that the user’s input is taken + as-is. This can be done as follows: + + ┌──── + │ (setq denote-file-name-slug-functions + │ '((title . denote-sluggify-title) + │ (keyword . identity) + │ (signature . denote-sluggify-signature))) + └──── + + The `identity' function simply returns the string it receives, thus + not altering it in any way. + + Another approach is to keep the sluggification but not downcase the + string. We can do this by modifying the original functions used by + Denote. For example, we have this: + + ┌──── + │ ;; The original function for reference + │ (defun denote-sluggify-title (str) + │ "Make STR an appropriate slug for title." + │ (downcase + │ (denote-slug-hyphenate + │ (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" str)))) + │ + │ ;; Our variant of the above, which does the same thing except from + │ ;; downcasing the string. + │ (defun my-denote-sluggify-title (str) + │ "Make STR an appropriate slug for title." + │ (denote-slug-hyphenate + │ (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" str))) + │ + │ ;; Now we use our function to sluggify titles without affecting their + │ ;; letter casing. + │ (setq denote-file-name-slug-functions + │ '((title . my-denote-sluggify-title) ; our function here + │ (signature . denote-sluggify-signature) + │ (keyword . denote-sluggify-keyword))) + └──── + + Follow this principle for all the sluggification functions ([Custom + sluggification to remove non-ASCII characters]). + + To access the source code, use either of the following built-in + methods: + + 1. Call the command `find-library' and search for `denote'. Then + navigate to the symbol you are searching for. + + 2. Invoke the command `describe-symbol', search for the symbol you are + interested in, and from the resulting Help buffer either click on + the first link or do `M-x help-view-source' (bound to `s' in Help + buffers, by default). + + Remember that deviating from the default file-naming scheme of Denote + will make things harder to use in the future, as files can/will have + permutations that create uncertainty. The sluggification scheme and + concomitant restrictions we impose by default are there for a very + good reason: they are the distillation of years of experience. Here we + give you what you wish, but bear in mind it may not be what you need. + You have been warned. + + +[Sluggification of file name components] See section 7.2 + +[The file-naming scheme] See section 7 + +[Custom sluggification to remove non-ASCII characters] See section 7.3.1 + +7.3.1 Custom sluggification to remove non-ASCII characters +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + A common use-case for Denote is to rename files such as videos + downloaded from the Internet. Sometimes, those files have Unicode + characters that (i) not all fonts support and (ii) create all sorts of + problems with pattern matching, such as when searching through file + names. + + By default, Denote does not remove Unicode characters because users + may actually want them (e.g. Latin characters with accents). Those who + do, however, wish to keep everything limited to the ASCII range can + use the following in their Emacs configuration ([User-defined + sluggification of file name components]). + + ┌──── + │ ;; These are the same as the default Denote sluggification functions, + │ ;; except they remove all non-ASCII characters. + │ (defun my-denote-sluggify-title (str) + │ (downcase + │ (denote-slug-hyphenate + │ (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" + │ (denote-slug-keep-only-ascii str))))) + │ + │ (defun my-denote-sluggify-signature (str) + │ (downcase + │ (denote-slug-put-equals + │ (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/-]*" "" + │ (denote-slug-keep-only-ascii str))))) + │ + │ (defun my-denote-sluggify-keyword (str) + │ (downcase + │ (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/_ =-]*" "" + │ (denote-slug-keep-only-ascii str)))) + │ + │ (defcustom denote-file-name-slug-functions + │ '((title . my-denote-sluggify-title) + │ (signature . my-denote-sluggify-signature) + │ (keyword . my-denote-sluggify-keyword))) + └──── + + +[User-defined sluggification of file name components] See section 7.3 + + +7.4 Features of the file-naming scheme for searching or filtering +───────────────────────────────────────────────────────────────── + + By default, file names have three fields and two sets of field + delimiters between them: + + ┌──── + │ DATE--TITLE__KEYWORDS.EXTENSION + └──── + + + When a signature is present, this becomes: + + ┌──── + │ DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION + └──── + + + Field delimiters practically serve as anchors for easier searching. + Consider this example: + + ┌──── + │ 20220621T062327==1a2--introduction-to-denote__denote_emacs.txt + └──── + + + You will notice that there are two matches for the word `denote': one + in the title field and another in the keywords’ field. Because of the + distinct field delimiters, if we search for `-denote' we only match + the first instance while `_denote' targets the second one. When + sorting through your notes, this kind of specificity is invaluable—and + you get it for free from the file names alone! Similarly, a search + for `=1' will show all notes that are related to each other by virtue + of their signature. + + Users can get a lot of value out of this simple yet effective + arrangement, even if they have no knowledge of regular expressions. + One thing to consider, for maximum effect, is to avoid using + multi-word keywords as those can get hyphenated like the title and + will thus interfere with the above: either set the user option + `denote-allow-multi-word-keywords' to nil or simply insert single + words at the relevant prompts. + + +8 Front matter +══════════════ + + Notes have their own “front matter”. This is a block of data at the + top of the file, with no empty lines between the entries, which is + automatically generated at the creation of a new note. The front + matter includes the title and keywords (aka “tags” or “filetags”, + depending on the file type) which the user specified at the relevant + prompt, as well as the date and unique identifier, which are derived + automatically. + + This is how it looks for Org mode (when `denote-file-type' is nil or + the `org' symbol): + + ┌──── + │ #+title: This is a sample note + │ #+date: [2022-06-30 Thu 16:09] + │ #+filetags: :denote:testing: + │ #+identifier: 20220630T160934 + └──── + + For Markdown with YAML (`denote-file-type' has the `markdown-yaml' + value), the front matter looks like this: + + ┌──── + │ --- + │ title: "This is a sample note" + │ date: 2022-06-30T16:09:58+03:00 + │ tags: ["denote", "testing"] + │ identifier: "20220630T160958" + │ --- + └──── + + For Markdown with TOML (`denote-file-type' has the `markdown-toml' + value), it is: + + ┌──── + │ +++ + │ title = "This is a sample note" + │ date = 2022-06-30T16:10:13+03:00 + │ tags = ["denote", "testing"] + │ identifier = "20220630T161013" + │ +++ + └──── + + And for plain text (`denote-file-type' has the `text' value), we have + the following: + + ┌──── + │ title: This is a sample note + │ date: 2022-06-30 + │ tags: denote testing + │ identifier: 20220630T161028 + │ --------------------------- + └──── + + The format of the date in the front matter is controlled by the user + option `denote-date-format'. When nil, Denote uses a + file-type-specific format: + + • For Org, an inactive timestamp is used, such as `[2022-06-30 Wed + 15:31]'. + + • For Markdown, the RFC3339 standard is applied: + `2022-06-30T15:48:00+03:00'. + + • For plain text, the format is that of ISO 8601: `2022-06-30'. + + If the value is a string, ignore the above and use it instead. The + string must include format specifiers for the date. These are + described in the doc string of `format-time-string'.. + + +8.1 Change the front matter format +────────────────────────────────── + + Per Denote’s design principles, the code is hackable. All front + matter is stored in variables that are intended for public use. We do + not declare those as “user options” because (i) they expect the user + to have some degree of knowledge in Emacs Lisp and (ii) implement + custom code. + + [ NOTE for tinkerers: code intended for internal use includes double + hyphens in its symbol. “Internal use” means that it can be changed + without warning and with no further reference in the change log. Do + not use any of it without understanding the consequences. ] + + The variables which hold the front matter format are: + + • `denote-org-front-matter' + + • `denote-text-front-matter' + + • `denote-toml-front-matter' + + • `denote-yaml-front-matter' + + These variables have a string value with specifiers that are used by + the `format' function. The formatting operation passes four arguments + which include the values of the given entries. If you are an advanced + user who wants to edit this variable to affect how front matter is + produced, consider using something like `%2$s' to control where the + Nth argument is placed. + + When editing the value, make sure to: + + 1. Not use empty lines inside the front matter block. + + 2. Insert at least one empty line after the front matter block and do + not use any empty line before it. + + These help with consistency and might prove useful if we ever need to + operate on the front matter as a whole. + + With those granted, below are some examples. The approach is the same + for all variables. + + ┌──── + │ ;; Like the default, but upcase the entries + │ (setq denote-org-front-matter + │ "#+TITLE: %s + │ #+DATE: %s + │ #+FILETAGS: %s + │ #+IDENTIFIER: %s + │ \n") + │ + │ ;; Change the order (notice the %N$s notation) + │ (setq denote-org-front-matter + │ "#+title: %1$s + │ #+filetags: %3$s + │ #+date: %2$s + │ #+identifier: %4$s + │ \n") + │ + │ ;; Remove the date + │ (setq denote-org-front-matter + │ "#+title: %1$s + │ #+filetags: %3$s + │ #+identifier: %4$s + │ \n") + │ + │ ;; Remove the date and the identifier + │ (setq denote-org-front-matter + │ "#+title: %1$s + │ #+filetags: %3$s + │ \n") + └──── + + Note that `setq' has a global effect: it affects the creation of all + new notes. Depending on the workflow, it may be preferrable to have a + custom command which `let' binds the different format. We shall not + provide examples at this point as this is a more advanced feature and + we are not yet sure what the user’s needs are. Please provide + feedback and we shall act accordingly. + + +8.2 Regenerate front matter +─────────────────────────── + + As part of version 4.0.0, the command `denote-add-front-matter' is + superseded by `denote-rename-file' and related ([Renaming + files]). Those commands will add missing front matter or rewrite the + modified lines of existing front matter. + + +[Renaming files] See section 6 + + +9 Linking notes +═══════════════ + + Denote offers several commands for linking between notes. Those use + the `denote:' hyperlink type. There are two types of links supported + by Denote: + + Direct links + A direct link points to a file inside the + `denote-directory'. The link is constructed by using the + `denote:' prefix and the target file’s identifier ([The + file-naming scheme]). This looks like + `denote:20250328T075526'. The syntax of a link depends on the + file type. For example, in Org and plain text links look like + `[[denote:20250328T075526][The title of the target file]]', + while in Markdown they are written as `[The title of the target + file](denote:20250328T075526)'. + + Query links + The `denote:' hyperlink type also supports special qualifiers + that change how the target of the link is interpreted. The + qualifier is a special token than tells Denote how to treat the + target of the link. It is written thus + `denote:TOKEN:QUERY'. There are two kinds of tokens: + `query-contents' and `query-filenames'. Those determine how the + query terms are used. As their names suggest, these two tokens + trigger a search in (i) the file contents of all readable files + or (ii) in the file names only. They are, in other words, + counterparts of the Unix `grep' and `find' programs, + respectively. + + The following sections cover all the details ([Why are some Org links + opening outside Emacs?]). + + +[The file-naming scheme] See section 7 + +[Why are some Org links opening outside Emacs?] See section 25.9 + +9.1 Add a single direct link using a file name prompt +───────────────────────────────────────────────────── + + The `denote-link' command (alias `denote-insert-link') inserts a link + at point to a file selected at the minibuffer prompt. Links are + formatted depending on the file type of the current note. In Org and + plain text buffers, links are formatted thus: + `[[denote:IDENTIFIER][DESCRIPTION]]'. While in Markdown they are + expressed as `[DESCRIPTION](denote:IDENTIFIER)'. + + When `denote-link' is called with a prefix argument (`C-u' by + default), it formats links like `[[denote:IDENTIFIER]]', regardless of + file type ([Fontify links in non-Org buffers]). The user might prefer + its simplicity. + + By default, the description of the link is determined thus: + + • If the region is active, its text becomes the description of the + link. In other words, the region text becomes the link. + • If the region is active but has no text, the description is empty + and so the link is formatted the same way as if using the `C-u' + prefix argument. + • If there is no region active, the description consists of the target + file’s signature and title, using the former only if it is present. + The title is retrieved either from the front matter or the file + name. + • If the target file has no signature, the title is used. + + To insert multiple such links at once, use the command + `denote-add-links' ([Insert links matching a regexp in their file + name]). + + If you want to directly link to a single file whose contents match a + given query, then use the command `denote-link-to-file-with-contents' + ([Adding a direct link to a file whose contents include the given + query]). + + Links are styled with the `denote-faces-link' face, which looks + exactly like an ordinary link by default. + + [ We optionally support direct links to a file followed by an extra + target to an Org headings ([The `denote-org-store-link-to-heading' + user option]). Other file types do not have the features of Org, so + we cannot generalise this. ] + + +[Fontify links in non-Org buffers] See section 9.14 + +[Insert links matching a regexp in their file name] See section 9.4 + +[Adding a direct link to a file whose contents include the given query] +See section 9.2 + +[The `denote-org-store-link-to-heading' user option] See section 9.7 + + +9.2 Add a direct link to a file whose contents include the given query +────────────────────────────────────────────────────────────────────── + + The `denote-link' command that we covered before prompts to select a + file among those in the `denote-directory' ([Adding a single direct + link using a file name prompt]). The match is done against the file’s + name. Users may, however, be interested to create a link to a file + whose contents include some text, regardless of how the file name is + called. To this end, the command `denote-link-to-file-with-contents', + (i) prompts for a query which is a plain string or regular expression, + (ii) if there are matching files, asks to select one among them, and + (iii) inserts the direct link at point. + + When called with an optional prefix argument (`C-u' by default), the + command `denote-link-to-file-with-contents' creates a link that does + not include a description for the target file: it just has the file’s + identifier (same as with `denote-link'). + + The command `denote-link-to-file-with-contents' is the counterpart of + `denote-link-to-all-files-with-contents' ([Insert links to all files + matching a query in their contents]). + + +[Adding a single direct link using a file name prompt] See section 9.1 + +[Insert links to all files matching a query in their contents] See +section 9.5 + + +9.3 Add a query link +──────────────────── + + As noted in the introduction to this section of the manual, the + `denote:' hyperlink type supports query links ([Linking + notes]). Unlike direct links, they do not point to any given + file. Instead, they trigger a search, whose results are displayed in a + separate buffer. + + Query links are expressed as `denote:TOKEN:QUERY', where `TOKEN' is + either `query-contents' or `query-filenames', while `QUERY' is a + string or Emacs regular expression to search for. + + The exact syntax of a query link depends on the file type. In Org and + plain text buffers, links are of the form + `[[denote:TOKEN:QUERY][QUERY]]'. In Markdown, they are formatted as + `[QUERY](denote:TOKEN:QUERY)'. In all cases, the description of the + link is the query text itself. + + The command `denote-query-contents-link' inserts a link at point that + triggers a search in the file contents of all readable documents in + the `denote-directory' ([Interact with the links buffer]). This is the + equivalent of the Unix `grep' command and uses the built-in Emacs Xref + interface ([Speed up backlinks’ or query links’ buffer creation?]). + Matches are displayed in a separate buffer, highlighting the exact + text while showing its context. + + The command `denote-query-filenames-link' creates a link at point that + initiates a search across file names in the `denote-directory'. This + is the equivalent of the Unix `find' command. Results are placed in a + Dired buffer ([Display filtered and sorted files with + `denote-sort-dired']). + + The user option `denote-query-links-display-buffer-action' controls + the placement of query link buffers. By default, they are designed to + appear below the current window. + + Query links are styled with the `denote-faces-query-link' face, which + looks a bit different that `denote-faces-link' (though this depends on + the active theme). + + +[Linking notes] See section 9 + +[Interact with the links buffer] See section 16 + +[Speed up backlinks’ or query links’ buffer creation?] See section 25.10 + +[Display filtered and sorted files with `denote-sort-dired'] See section +14 + + +9.4 Insert links to all files matching a query in their file name +───────────────────────────────────────────────────────────────── + + The command `denote-add-links' adds links at point to all file names + in the `denote-directory' that match a regular expression or plain + string. This is similar to the `denote-link' command, which + establishes a direct link to a specified file ([Adding a single direct + link]). Links to files whose names match the given search terms are + inserted as a typographic list, such as: + + ┌──── + │ - link1 + │ - link2 + │ - link3 + └──── + + Each link is formatted according to the file type of the current note, + as explained further above about the `denote-link' command. The + current note is excluded from the matching entries (adding a link to + itself is pointless). + + When called with a prefix argument (`C-u') `denote-add-links' will + format all links as `[[denote:IDENTIFIER]]', hence a typographic list: + + ┌──── + │ - [[denote:IDENTIFIER-1]] + │ - [[denote:IDENTIFIER-2]] + │ - [[denote:IDENTIFIER-3]] + └──── + + Same examples of a regular expression that can be used with this + command: + + • `journal' match all files which include `journal' anywhere in their + name. + + • `_journal' match all files which include `journal' as a keyword. + + • `^2022.*_journal' match all file names starting with `2022' and + including the keyword `journal'. + + • `\.txt' match all files including `.txt'. In practical terms, this + only applies to the file extension, as Denote automatically removes + dots (and other characters) from the base file name. + + If files are created with `denote-sort-keywords' as non-nil (the + default), then it is easy to write a regexp that includes multiple + keywords in alphabetic order: + + • `_denote.*_package' match all files that include both the `denote' + and `package' keywords, in this order. + + • `\(.*denote.*package.*\)\|\(.*package.*denote.*\)' is the same as + above, but out-of-order. + + Remember that regexp constructs only need to be escaped once (like + `\|') when done interactively but twice when called from Lisp. What + we show above is for interactive usage. + + Links are created only for files which qualify as a “note” for our + purposes ([Linking notes]). + + +[Adding a single direct link] See section 9.1 + +[Linking notes] See section 9 + + +9.5 Insert links to all files matching a query in their contents +──────────────────────────────────────────────────────────────── + + The aforementioned `denote-add-links' command takes a query that + matches it against file names ([Insert links to all files matching a + query in their file name]). It then creates a typographic list + (bullet list) with direct links to all the matching files. Users who + wish to achieve the same result but have the query be matched against + file contents (not file names), can use the command + `denote-link-to-all-files-with-contents'. + + The command `denote-link-to-all-files-with-contents' is the + counterpart of `denote-link-to-file-with-contents' ([Add a direct link + to a file whose contents include the given query]). + + +[Insert links to all files matching a query in their file name] See +section 9.4 + +[Add a direct link to a file whose contents include the given query] See +section 9.2 + + +9.6 The `denote-open-link-function' user option +─────────────────────────────────────────────── + + The user option `denote-open-link-function' specifies the function + used by Denote to open the file of a link. The default value opens the + file in the other window. Another common value is the function + `find-file', which will open the file in the current window. Users may + also specify a function of their choosing. + + Note that this is relevant in buffers other than Org mode because Org + has its own mechanism for how to open links (read the documentation of + the command `org-open-at-point'). + + +9.7 The `denote-org-store-link-to-heading' user option +────────────────────────────────────────────────────── + + The user option `denote-org-store-link-to-heading' determines whether + `org-store-link' links to the current Org heading. + + [ Remember that what `org-store-link' does is merely collect a link. + To actually insert it, use the command `org-insert-link'. Note that + `org-capture' uses `org-store-link' internally when it needs to + store a link. ] + + When the value is nil, the Denote handler for `org-store-link' + produces links only to the current file (by using the file’s + identifier). For example: + + ┌──── + │ [[denote:20240118T060608][Some test]] + └──── + + + If the value is `context', the link consists of the file’s identifier + and the text of the current heading, like this: + + ┌──── + │ [[denote:20240118T060608::*Heading text][Some test::Heading text]]. + └──── + + + However, if there already exists a `CUSTOM_ID' property for the + current heading, this is always given priority and is used instead of + the context. + + If the value is `id' or, for backward-compatibility, any other non-nil + value, then Denote will use the standard Org mechanism of the + `CUSTOM_ID' property to create a unique link to the heading. If the + heading does not have a `CUSTOM_ID', it creates it and includes it in + its `PROPERTIES' drawer. If a `CUSTOM_ID' exists, it takes it as-is. + The result is like this: + + ┌──── + │ [[denote:20240118T060608::#h:eed0fb8e-4cc7-478f][Some test::Heading text]] + └──── + + + The value of the `CUSTOM_ID' is determined by the Org user option + `org-id-method'. The sample shown above uses the default UUID + infrastructure (though I deleted a few characters to not get + complaints from the byte compiler about long lines in the doc + string…). + + Note that this option does not affect how Org behaves with regard to + `org-id-link-to-org-use-id'. If that user option is set to create `ID' + properties, then those will be created by Org even if the Denote link + handler will take care to not use/store the `ID' value. Concretely, + users who never want `ID' properties under their headings should keep + `org-id-link-to-org-use-id' in its nil value. + + Context links are easier to break than those with a `CUSTOM_ID' in + cases where either the heading text changes or there is another + heading that matches that text. The potential advantage of context + links is that they do not require a `PROPERTIES' drawer. + + When visiting a link to a heading, Org opens the Denote file and then + navigates to that heading. + + [ This feature only works in Org mode files, as other file types do + not have a linking mechanism that handles unique identifiers for + headings or other patterns to jump to. If `org-store-link' is + invoked in one such file, it captures only the Denote identifier of + the file, even if this user option is set to a non-nil value. ] + + +9.8 Adding direct links to files matching contents +────────────────────────────────────────────────── + + +9.9 Insert links from marked files in Dired +─────────────────────────────────────────── + + The command `denote-link-dired-marked-notes' is similar to + `denote-add-links' in that it inserts in the buffer a typographic list + of links to Denote notes ([Insert links matching a regexp]). Though + instead of reading a regular expression, it lets the user mark files + in Dired and link to them. This should be easier for users of all + skill levels, instead of having to write a potentially complex regular + expression. + + If there are multiple buffers that visit a Denote note, this command + will ask to select one among them, using minibuffer completion. If + there is only one buffer, it will operate in it outright. If there + are no buffers, it will produce an error. + + With optional `ID-ONLY' as a prefix argument (`C-u' by default), the + command inserts links with just the identifier, which is the same + principle as with `denote-link' and others ([Adding a single link]). + + The command `denote-link-dired-marked-notes' is meant to be used from + a Dired buffer. + + As always, links are created only for files which qualify as a “note” + for our purposes ([Linking notes]). + + The `denote-dired-link-marked-notes' is an alias for + `denote-link-dired-marked-notes'. + + +[Insert links matching a regexp] See section 9.4 + +[Adding a single link] See section 9.1 + +[Linking notes] See section 9 + + +9.10 Link to an existing note or create a new one +───────────────────────────────────────────────── + + In one’s note-taking workflow, there may come a point where they are + expounding on a certain topic but have an idea about another subject + they would like to link to ([Linking notes]). The user can always + rely on the other linking facilities we have covered herein to target + files that already exist. Though they may not know whether they + already have notes covering the subject or whether they would need to + write new ones. To this end, Denote provides two convenience + commands: + + `denote-link-after-creating' + Create new note in the background and link to it directly. + + Use `denote' interactively to produce the new note. Its doc + string or this manual explains which prompts will be used and + under what conditions ([Standard note creation]). + + With optional `ID-ONLY' as a prefix argument (this is the `C-u' + key, by default) create a link that consists of just the + identifier. Else try to also include the file’s title. This + has the same meaning as in `denote-link' ([Adding a single + link]). + + IMPORTANT NOTE: Normally, `denote' does not save the buffer it + produces for the new note ([The + `denote-save-buffer-after-creation' option]). This is a safety + precaution to not write to disk unless the user wants it + (e.g. the user may choose to kill the buffer, thus cancelling + the creation of the note). However, for this command the + creation of the note happens in the background and the user may + miss the step of saving their buffer. We thus have to save the + buffer in order to (i) establish valid links, and (ii) retrieve + whatever front matter from the target file. + + `denote-link-after-creating-with-command' + This command is like `denote-link-after-creating' except it + prompts for a note-creating command ([Points of entry]). Use + this to, for example, call `denote-signature' so that the newly + created note has a signature as part of its file name. Optional + `ID-ONLY' has the same meaning as in the command + `denote-link-after-creating'. + + `denote-link-or-create' + Use `denote-link' on `TARGET' file, creating it if necessary. + + If `TARGET' file does not exist, call + `denote-link-after-creating' which runs the `denote' command + interactively to create the file. The established link will + then be targeting that new file. + + If `TARGET' file does not exist, add the user input that was + used to search for it to the history of the + `denote-file-prompt'. The user can then retrieve and possibly + further edit their last input, using it as the newly created + note’s actual title. At the `denote-file-prompt' type `M-p' + with the default key bindings, which calls + `previous-history-element'. + + With optional `ID-ONLY' as a prefix argument create a link with + just the file’s identifier. This has the same meaning as in + `denote-link'. + + This command has the alias + `denote-link-to-existing-or-new-note', which helps with + discoverability. + + In all of the above, an optional prefix argument (`C-u' by default) + creates a link that consists of just the identifier. This has the + same meaning as in the regular `denote-link' command. + + Denote provides similar functionality for opening an existing note or + creating a new one ([Open an existing note or create it if missing]). + + +[Linking notes] See section 9 + +[Standard note creation] See section 5.1 + +[Adding a single link] See section 9.1 + +[The `denote-save-buffer-after-creation' option] See section 5.1.5 + +[Points of entry] See section 5 + +[Open an existing note or create it if missing] See section 5.6 + + +9.11 The backlinks’ buffer +────────────────────────── + + [ Older versions of Denote had two types of formatting for the + backlinks’ buffer. As part of version `4.0.0', we only support the + standard Xref view which shows matches in their context. The user + option `denote-backlinks-show-context' is thus removed. ] + + The command `denote-backlinks' (alias `denote-show-backlinks-buffer') + produces a bespoke buffer which displays backlinks to the current note + ([Interact with the links buffer]). A “backlink” is a link back to the + present entry. Backlinks can be generated for any file type that has a + Denote file-naming scheme, such as PDFs, images, and videos, as well + as the regular plain text files. + + The backlinks’ buffer is, in essence, the equivalent of a Unix `grep' + command across the `denote-directory' ([Speed up backlinks’ buffer + creation?]). It groups matches by file name, while it displays the + line on which a link to the current file occurs together with its + context. It looks like this (plus the appropriate fontification): + + ┌──── + │ Backlinks to "On being honest" (20220614T130812) + │ ------------------------------------------------ + │ + │ 20220614T145606--let-this-glance-become-a-stare__journal.txt + │ 37: growing into it: [[denote:20220614T130812][On being honest]]. + │ 64: As I said in [[denote:20220614T130812][On being honest]] I have never + │ 20220616T182958--feeling-butterflies-in-your-stomach__journal.txt + │ 62: indifference. In [[denote:20220614T130812][On being honest]] I alluded + └──── + + Note that the width of the lines in the context depends on the + underlying file. In the above example, the lines are split at the + `fill-column'. Long lines will show up just fine. Also note that the + built-in user option `xref-truncation-width' can truncate long lines + to a given maximum number of characters. + + As with query links, the backlinking facility uses Emacs’ built-in + Xref infrastructure ([Adding a query link]). On some operating + systems, the user may need to add certain executables to the relevant + environment variable ([Why do I get “Search failed with status 1” when + I search for backlinks?]). + + The placement of the backlinks’ buffer is subject to the user option + `denote-backlinks-display-buffer-action'. Due to the nature of the + underlying `display-buffer' mechanism, this inevitably is a relatively + advanced feature. By default, the backlinks’ buffer is displayed below + the current window. + + Backlinks to the current file can also be visited by using the + minibuffer completion interface with the `denote-find-backlink' + command ([Visiting linked files via the minibuffer]). + + +[Interact with the links buffer] See section 16 + +[Speed up backlinks’ buffer creation?] See section 25.10 + +[Adding a query link] See section 9.3 + +[Why do I get “Search failed with status 1” when I search for +backlinks?] See section 25.11 + +[Visiting linked files via the minibuffer] See section 9.13 + + +9.12 Writing metanotes +────────────────────── + + A “metanote” is an entry that describes other entries who have + something in common. Writing metanotes can be part of a workflow + where the user periodically reviews their work in search of patterns + and deeper insights. For example, you might want to read your journal + entries from the past year to reflect on your experiences, evolution + as a person, and the like. + + The commands `denote-add-links', `denote-link-dired-marked-notes' are + suited for this task. + + [Insert links matching a regexp]. + + [Insert links from marked files in Dired]. + + You will create your metanote the way you use Denote ordinarily + (metanotes may have the `metanote' keyword, among others), write an + introduction or however you want to go about it, invoke the command + which inserts multiple links at once (see the above-cited nodes), and + continue writing. + + Metanotes can serve as entry points to groupings of individual notes. + They are not the same as a filtered list of files, i.e. what you would + do in Dired or the minibuffer where you narrow the list of notes to a + given query. Metanotes contain the filtered list plus your thoughts + about it. The act of purposefully grouping notes together and + contemplating on their shared patterns is what adds value. + + Your future self will appreciate metanotes for the function they serve + in encapsulating knowledge, while current you will be equipped with + the knowledge derived from the deliberate self-reflection. + + +[Insert links matching a regexp] See section 9.4 + +[Insert links from marked files in Dired] See section 9.9 + + +9.13 Visiting linked files via the minibuffer +───────────────────────────────────────────── + + Denote has a major-mode-agnostic mechanism to collect all linked file + references in the current buffer and return them as an appropriately + formatted list. This list can then be used in interactive commands. + The `denote-find-link' is such a command. It uses minibuffer + completion to visit a file that is linked to from the current note. + The candidates have the correct metadata, which is ideal for + integration with other standards-compliant tools ([Extending Denote]). + For instance, a package such as `marginalia' will display accurate + annotations, while the `embark' package will be able to work its magic + such as in exporting the list into a filtered Dired buffer (i.e. a + familiar Dired listing with only the files of the current minibuffer + session). + + To visit backlinks to the current note via the minibuffer, use + `denote-find-backlink'. This is an alternative to placing backlinks + in a dedicated buffer ([The backlinks’ buffer]). + + +[Extending Denote] See section 19 + +[The backlinks’ buffer] See section 9.11 + + +9.14 Fontify links in non-Org buffers +───────────────────────────────────── + + Denote links are automatically fontified in Org buffers ([Adding a + single link]). This means that Org recognises the link and applies + the relevant properties to it to make it clickable/actionable. Other + major modes, such as `markdown-mode' (for `.md' files) or `text-mode' + (for `.txt' files) do not have this feature built into them. Users can + still get the same behaviour as with Org by activating the + `denote-fontify-links-mode'. + + The `denote-fontify-links-mode' is a buffer-local minor mode. Users + can enable it automatically in plain text files that correspond to + denote notes with something like this: + + ┌──── + │ (add-hook 'text-mode-hook #'denote-fontify-links-mode-maybe) + └──── + + The `text-mode-hook' applies to all modes derived from `text-mode', + including `markdown-mode'. Though a more explicit setup does no harm: + + ┌──── + │ (add-hook 'markdown-mode-hook #'denote-fontify-links-mode-maybe) + └──── + + Because Org already recognises `denote:' links, the function + `denote-fontify-links-mode-maybe' will not enable the mode + `denote-fontify-links-mode' in Org buffers. + + In files whose major mode is `markdown-mode', the default key binding + `C-c C-o' (which calls the command `markdown-follow-thing-at-point') + correctly resolves `denote:' links. Interested users can refer to the + function `denote-link-markdown-follow' for the implementation details. + + +[Adding a single link] See section 9.1 + + +9.15 The `denote-link-description-format' to format link descriptions +───────────────────────────────────────────────────────────────────── + + The user option `denote-link-description-format' controls how the + command `denote-link' and related functions create a link description + by default. + + The value can be either a function or a string. If it is a function, + it is called with one argument, the file, and should return a string + representing the link description. + + The default is a function that returns the active region or the title + of the note (with the signature if present). + + If the value is a string, it treats specially the following + specifiers: + + • The `%t' is the Denote `TITLE' in the front matter or the file name. + • The `%T' is the Denote `TITLE' in the file name. + • The `%i' is the Denote `IDENTIFIER' of the file. + • The `%I' is the identifier converted to `DAYNAME, DAYNUM MONTHNUM + YEAR'. + • The `%d' is the same as `%i' (`DATE' mnemonic). + • The `%D' is a “do what I mean” which behaves the same as `%t' and if + that returns nothing, it falls back to `%I', then `%i'. + • The `%d' is the same as `%i' (`DATE' mnemonic). + • The `%s' is the Denote `SIGNATURE' of the file. + • The `%k' is the Denote `KEYWORDS' of the file. + • The `%%' is a literal percent sign. + + In addition, the following flags are available for each of the + specifiers: + + 0 + Pad to the width, if given, with zeros instead of spaces. + - + Pad to the width, if given, on the right instead of the left. + < + Truncate to the width and precision, if given, on the left. + > + Truncate to the width and precision, if given, on the right. + ^ + Convert to upper case. + _ + Convert to lower case. + + When combined all together, the above are written thus: + + ┌──── + │ %SPECIFIER-CHARACTER + └──── + + + Any other text in the string it taken as-is. Users may want, for + example, to include some text that makes Denote links stand out, such + as a `[D]' prefix. + + If the region is active, its text is used as the link’s description. + + +10 Choose which commands to prompt for +══════════════════════════════════════ + + The user option `denote-commands-for-new-notes' specifies a list of + commands that are available at the `denote-command-prompt'. This + prompt is used by Denote commands that ask the user how to create a + new note, as described elsewhere in this manual: + + • [Open an existing note or create it if missing] + • [Link to a note or create it if missing] + + The default value includes all the basic file-creating commands + ([Points of entry]). Users may customise this value if (i) they only + want to see fewer options and/or (ii) wish to include their own custom + command in the list ([Write your own convenience commands]). + + +[Open an existing note or create it if missing] See section 5.6 + +[Link to a note or create it if missing] See section 9.10 + +[Points of entry] See section 5 + +[Write your own convenience commands] See section 5.1.4.1 + + +11 Fontification in Dired +═════════════════════════ + + One of the upsides of Denote’s file-naming scheme is the predictable + pattern it establishes, which appears as a near-tabular presentation + in a listing of notes (i.e. in Dired). The `denote-dired-mode' can + help enhance this impression, by fontifying the components of the file + name to make the date (identifier) and keywords stand out. + + There are two ways to set the mode. Either use it for all + directories, which probably is not needed: + + ┌──── + │ (add-hook 'dired-mode-hook #'denote-dired-mode) + └──── + + Or configure the user option `denote-dired-directories' and then set + up the function `denote-dired-mode-in-directories': + + ┌──── + │ ;; We use different ways to specify a path for demo purposes. + │ (setq denote-dired-directories + │ (list denote-directory + │ (thread-last denote-directory (expand-file-name "attachments")) + │ (expand-file-name "~/Documents/vlog"))) + │ + │ (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) + └──── + + The user option `denote-dired-directories-include-subdirectories' + specifies whether the `denote-dired-directories' also cover their + subdirectories. By default they do not. Set this option to `t' to + include subdirectories as well. + + The faces we define for this purpose are: + + ⁃ `denote-faces-date' + ⁃ `denote-faces-delimiter' + ⁃ `denote-faces-extension' + ⁃ `denote-faces-keywords' + • `denote-faces-signature' + ⁃ `denote-faces-subdirectory' + ⁃ `denote-faces-time' + ⁃ `denote-faces-title' + + For more control, we also provide these: + + #+vindex denote-faces-year +vindex denote-faces-month +vindex + #denote-faces-day +vindex denote-faces-hour +vindex + #denote-faces-minute +vindex denote-faces-second + ⁃ `denote-faces-year' + ⁃ `denote-faces-month' + ⁃ `denote-faces-day' + ⁃ `denote-faces-hour' + ⁃ `denote-faces-minute' + ⁃ `denote-faces-second' + + For the time being, the `diredfl' package is not compatible with this + facility. + + The `denote-dired-mode' does not only fontify note files that were + created by Denote: it covers every file name that follows our naming + conventions ([The file-naming scheme]). This is particularly useful + for scenaria where, say, one wants to organise their collection of + PDFs and multimedia in a systematic way (and, perhaps, use them as + attachments for the notes Denote produces if you are writing Org notes + and are using its standand attachments’ facility). + + +[The file-naming scheme] See section 7 + + +12 Automatically rename Denote buffers +══════════════════════════════════════ + + The minor mode `denote-rename-buffer-mode' provides the means to + automatically rename the buffer of a Denote file upon visiting the + file. This applies both to existing Denote files as well as new ones + ([Points of entry]). Enable the mode thus: + + ┌──── + │ (denote-rename-buffer-mode 1) + └──── + + Buffers are named by applying the function specified in the user + option `denote-rename-buffer-function'. The default function is + `denote-rename-buffer': it renames the buffer based on the template + set in the user option `denote-rename-buffer-format'. By default, the + formatting template targets only the `TITLE' component of the file + name ([The file-naming scheme]). Other fields are explained elsewhere + in this manual ([The denote-rename-buffer-format]). + + Note that renaming a buffer is not the same as renaming a file + ([Renaming files]). The former is just for convenience inside of + Emacs. Whereas the latter is for writing changes to disk, making them + available to all programs. + + +[Points of entry] See section 5 + +[The file-naming scheme] See section 7 + +[The denote-rename-buffer-format] See section 12.1 + +[Renaming files] See section 6 + +12.1 The `denote-rename-buffer-format' option +───────────────────────────────────────────── + + The user option `denote-rename-buffer-format' controls how the + function `denote-rename-buffer' chooses the name of the + buffer-to-be-renamed. + + The value of this user option is a string. The following specifiers + are placeholders for Denote file name components ([The file-naming + scheme]): + + • The `%t' is the Denote `TITLE' in the front matter or the file name. + • The `%T' is the Denote `TITLE' in the file name. + • The `%i' is the Denote `IDENTIFIER' of the file. + • The `%I' is the identifier converted to `DAYNAME, DAYNUM MONTHNUM + YEAR'. + • The `%d' is the same as `%i' (`DATE' mnemonic). + • The `%D' is a “do what I mean” which behaves the same as `%t' and if + that returns nothing, it falls back to `%I', then `%i'. + • The `%s' is the Denote `SIGNATURE' of the file. + • The `%k' is the Denote `KEYWORDS' of the file. + • The `%b' is an indicator of whether or not the file has backlinks + pointing to it. The indicator string is defined in the user option + `denote-rename-buffer-backlinks-indicator', alias + `denote-buffer-has-backlinks-string'. + • The `%%' is a literal percent sign. + + In addition, the following flags are available for each of the + specifiers: + + `0' + Pad to the width, if given, with zeros instead of spaces. + `-' + Pad to the width, if given, on the right instead of the left. + `<' + Truncate to the width and precision, if given, on the left. + `>' + Truncate to the width and precision, if given, on the right. + `^' + Convert to upper case. + `_' + Convert to lower case. + + When combined all together, the above are written thus: + + ┌──── + │ %SPECIFIER-CHARACTER + └──── + + + Any other string it taken as-is. Users may want, for example, to + include some text that makes Denote buffers stand out, such as a `[D]' + prefix. Examples: + + ┌──── + │ ;; The following is the default value. Use a literal [D] prefix, + │ ;; followed by the title and then the backlinks indicator. If there + │ ;; is no title, use the identifier in its human-readable date + │ ;; representation, and if that is not possible, use the identifier + │ ;; as-is. + │ (setq denote-rename-buffer-format "[D] %D%b") + │ + │ ;; Customize what the backlink indicator looks like. This two-faced + │ ;; arrow is the default. + │ (setq denote-rename-buffer-backlinks-indicator "<-->") + │ + │ ;; Use just the title and keywords with some emoji in between, because + │ ;; why not? + │ (setq denote-rename-buffer-format "%t 🤨 %k") + │ + │ ;; Use the title with a literal "[D]" before it. + │ (setq denote-rename-buffer-format "[D] %t") + │ + │ ;; As above, but also add the `denote-rename-buffer-backlinks-indicator' at the end. + │ (setq denote-rename-buffer-format "[D] %t%b") + └──── + + Users who need yet more flexibility are best served by writing their + own function and assigning it to the `denote-rename-buffer-function'. + + +[The file-naming scheme] See section 7 + + +13 Use Org dynamic blocks +═════════════════════════ + + This section is about the external package `denote-org' (by + Protesilaos). The code of `denote-org' used to be available as part of + the main `denote' package, but we decided to keep each optional + extension as a separate package to make things easier to maintain and + to understand. + + Denote can optionally integrate with Org mode’s “dynamic blocks” + facility. This means that it can use special blocks that are evaluated + with `C-c C-x C-u' (`org-dblock-update') to generate their contents. + + Dynamic blocks are particularly useful for metanote entries that + reflect on the status of earlier notes ([Writing metanotes]). The + `denote-org' package defines many of these Org dynamic blocks. + + ⁃ Package name (GNU ELPA): `denote-org' + ⁃ Official manual: + ⁃ Git repository: + ⁃ Backronym: Denote… Ordinarily Restricts Gyrations. + + +[Writing metanotes] See section 9.12 + + +14 Display filtered and sorted files with `denote-sort-dired' or `denote-dired' +═══════════════════════════════════════════════════════════════════════════════ + + The `denote.el' file contains functions which empower user or + developers to sort files by the given file name component ([The + file-naming scheme]). + + The command `denote-sort-dired' (alias `denote-dired') produces a + Dired file listing with a flat, filtered, and sorted set of files from + the `denote-directory' ([Define a sorting function per component]). It + does so by a series of prompts, which can be configured with the user + option `denote-sort-dired-extra-prompts' ([Configure what extra + prompts `denote-sort-dired' issues]). + + Think of `denote-sort-dired' as the counterpart to the Unix `find' + command. While `denote-grep' corresponds to the Unix `grep' ([Use + `denote-grep' to search inside files]). + + The out-of-the-box behaviour of `denote-sort-dired' is as follows: + + 1. It first asks for a regular expression with which to match Denote + file names. Remember that due to Denote’s efficient file-naming + scheme, you usually do not need to write some complex regular + expression. For example, something like `_journal' will match only + files with a `journal' keyword. + 2. Once the regular expression is provided, the command asks for a + Denote file name component to sort files by. This is a symbol among + `title', `keywords', `signature', and `identifier' ([Define a + sorting function per component]). + 3. Finally, it asks a “yes or no” on whether to reverse the sort + order. + + The resulting listing is a regular Dired buffer, unlike that of + `dired-virtual-mode' ([Use `dired-virtual-mode' for arbitrary file + listings]). + + The sorting mechanism can be used by other packages to achieve their + ends. As an example, the dynamic Org blocks that the `denote-org' + package (by Protesilaos) defines also use this feature internally by + means of the non-interactive function `denote-sort-files'. + + +[The file-naming scheme] See section 7 + +[Define a sorting function per component] See section 14.2 + +[Configure what extra prompts `denote-sort-dired' issues] See section +14.1 + +[Use `denote-grep' to search inside files] See section 15 + +[Use `dired-virtual-mode' for arbitrary file listings] See section 19.6 + +14.1 Configure what extra prompts `denote-sort-dired' issues +──────────────────────────────────────────────────────────── + + By default, the `denote-sort-dired' command prompts for (i) a query to + match file names, (ii) a file name component to sort by, and (iii) + whether to reverse the sorting ([Display filtered and sorted files + with denote-sort-dired]). Users can configure the latter two by + modifying the user option `denote-sort-dired-extra-prompts'. + + The `denote-sort-dired-extra-prompts' accepts either a nil value or a + list of symbols among `sort-by-component', `reverse-sort', and + `exclude-regexp'. The order those symbols appear in the list is + significant, with the leftmost coming first. + + These symbols correspond to the following: + + • A choice to select the file name component to sort by. + • A yes or no prompt on whether to reverse the sorting. + • A string (or regular expression) of files to be excluded from the + results. + + In case of a nil value, those extra prompts will not happen, meaning + that `denote-sort-dired' will fall back to using whatever is defined + in the variables `denote-sort-dired-default-sort-component' and + `denote-sort-dired-default-reverse-sort'. + + Here are some examples: + + ┌──── + │ ;; The default extra prompts... + │ (setq denote-sort-dired-extra-prompts '(sort-by-component reverse-sort)) + │ + │ ;; When using `denote-sort-dired', ask whether to reverse the sort and + │ ;; then which file name component to sort by. These are always done + │ ;; after the prompt to search for files matching a regexp. + │ (setq denote-sort-dired-extra-prompts '(reverse-sort sort-by-component)) + │ + │ ;; Do not prompt for a reverse sort. Just use the value of + │ ;; `denote-sort-dired-default-reverse-sort' (which is nil out-of-the-box). + │ (setq denote-sort-dired-extra-prompts '(sort-by-component)) + │ + │ ;; Do not issue any extra prompts. Always sort by the `title' file + │ ;; name component and never do a reverse sort. + │ (setq denote-sort-dired-extra-prompts nil) + │ (setq denote-sort-dired-default-sort-component 'title) + │ (setq denote-sort-dired-default-reverse-sort nil) + └──── + + +[Display filtered and sorted files with denote-sort-dired] See section +14 + + +14.2 Define a sorting function per component +──────────────────────────────────────────── + + When sorting by `title', `keywords', or `signature' with the + `denote-sort-dired' command, Denote will internally apply a sorting + function that is specific to each component ([Configure what extra + prompts `denote-sort-dired' issues]). These are subject to user + configuration: + + • `denote-sort-identifier-comparison-function' + + • `denote-sort-title-comparison-function' + + • `denote-sort-keywords-comparison-function' + + • `denote-sort-signature-comparison-function' + + By default, all these user options use the same sorting function, + namely `string-collate-lessp'. Users who have specific needs for any + of those file name components can write their own sorting algorithms + ([Sort signatures that include Luhmann-style sequences]). + + +[Configure what extra prompts `denote-sort-dired' issues] See section +14.1 + +[Sort signatures that include Luhmann-style sequences] See section +14.2.1 + +14.2.1 Sort signatures that include Luhmann-style sequences +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + [ The `denote-sequence' package (by Protesilaos) covers this use-case + and many others ([Write sequence notes or folgezettel]). It is the + superior option for anyone interested in this functionality. We keep + the code below for reference, as there may be users of it who need + to revisit it. Though long-term, it is better to use + `denote-sequence'. ] + + Niklas Luhmann would edit notes to form sequences of thoughts with + branching paths, such as `1.1', `1.1a', `1.2', `1.2a', `1.2b', etc. + With the Denote file-naming scheme, we make the word separator in each + file name component use the same character as the entire field, so + words in a title have a dash between them and signatures have the + equals sign ([The file-naming scheme]). Thus, our Luhmann-style + signature will be slightly different in their looks: `1=1', `1=1a', + `1=2', `1=2a', `1=2b'. + + When using the `denote-sort-dired' command with default settings, our + signatures will not sort in an intuitive way. This is because they + combine numbers and letters, which require a different approach than + what the default sorting function is using ([Define a sorting function + per component]). In the following code block, we show a sorting + algorithm that should do the right thing while dealing with + Luhmann-style signatures. + + ┌──── + │ (defun my-denote--split-luhman-sig (signature) + │ "Split numbers and letters in Luhmann-style SIGNATURE string." + │ (replace-regexp-in-string + │ "\\([a-zA-Z]+?\\)\\([0-9]\\)" "\\1=\\2" + │ (replace-regexp-in-string + │ "\\([0-9]+?\\)\\([a-zA-Z]\\)" "\\1=\\2" + │ signature))) + │ + │ (defun my-denote--pad-sig (signature) + │ "Create a new signature with padded spaces for all components" + │ (combine-and-quote-strings + │ (mapcar + │ (lambda (x) + │ (string-pad x 5 32 t)) + │ (split-string (my-denote--split-luhman-sig signature) "=" t)) + │ "=")) + │ + │ (defun my-denote-sort-for-signatures (sig1 sig2) + │ "Return non-nil if SIG1 is smaller that SIG2. + │ Perform the comparison with `string<'." + │ (string< (my-denote--pad-sig sig1) (my-denote--pad-sig sig2))) + │ + │ ;; Change the sorting function only when we sort by signature. + │ (setq denote-sort-signature-comparison-function #'my-denote-sort-for-signatures) + └──── + + +[Write sequence notes or folgezettel] See section 18.2 + +[The file-naming scheme] See section 7 + +[Define a sorting function per component] See section 14.2 + + +15 Use `denote-grep' to search inside files +═══════════════════════════════════════════ + + The command `denote-grep' searches for the given query across all + readable files in the `denote-directory'. It puts the collected + results in an Xref buffer (just like with our backlinks and query + links functionality). In this buffer, users can do `M-x describe-mode' + (`C-h m' with default key bindings) to learn about all the actions + they can perform and the keys they are bound to ([Interact with the + links buffer]). + + Think of `denote-grep' as the counterpart to the Unix `grep' command. + While `denote-sort-dired' corresponds to the Unix `find' ([Display + filtered and sorted files with `denote-sort-dired']). + + The command `denote-grep-marked-dired-files' is like `denote-grep' but + operates on the files that are marked in a Dired buffer. + + The command `denote-grep-files-referenced-in-region' is like + `denote-grep' for any files referenced within the boundaries of the + marked region. Files are referenced by their identifier. This includes + links with just the identifier (as described in `denote-link' and + related ([Add a single direct link using a file name prompt])), links + written by an Org dynamic block (see the `denote-org' package ([Use + Org dynamic blocks])), or even file listings such as those of `dired' + and the command-line `ls' program. + + The user option `denote-grep-display-buffer-action' controls where the + buffer with the search results is displayed at. By default, they + appear in the same window where the command `denote-grep' is called + from. + + +[Interact with the links buffer] See section 16 + +[Display filtered and sorted files with `denote-sort-dired'] See section +14 + +[Add a single direct link using a file name prompt] See section 9.1 + +[Use Org dynamic blocks] See section 13 + + +16 Interact with the links buffer +═════════════════════════════════ + + Denote commands, such as `denote-grep', `denote-backlinks', and + `denote-query-contents-link', produce an Xref buffer with search + results ([Speed up backlinks’ or query links’ buffer creation?]). + Matching lines are grouped by the file name they belong to. + + • [Use `denote-grep' to search inside files]. + • [The backlinks’ buffer]. + • [Add a query link]. + + This buffer uses the major mode `denote-query-mode'. It binds commands + to keys in the `denote-query-mode-map'. Those allow users to filter + the output of the last search. Here, “last search” refers to the list + of files that were returned by whichever command produced the buffer + (e.g. the last `denote-grep'). + + `denote-query-focus-last-search' + Perform a search in the contents of files that were matched by + the last search. + + `denote-query-exclude-files' + Exclude files from the last search whose name matches the given + input. + + `denote-query-only-include-files' + Only keep files from the last search whose name matches the + given input. + + `denote-query-exclude-files-with-keywords' + Exclude files from the last search whose name includes the given + keywords. + + `denote-query-only-include-files-with-keywords' + Only keep files from the last search whose name includes the + given keywords. + + `denote-query-clear-all-filters' + Clear all the applied filters. + + Remember that these are easy to use even without knowledge of regular + expressions, thanks to the efficiency of the Denote file-naming scheme + ([Features of the file-naming scheme for searching or filtering]). For + instance, to exclude notes with the keyword `philosophy' from current + search buffer, use `denote-query-exclude-files' and then type + `_philosophy' as your input. + + In addition to those filtering options, the `denote-query-mode' also + allows provides an outline mechanism to hide or show the matches as + these are grouped per file. There also are some of the default actions + provided by the Xref infrastructure. Users can do `M-x describe-mode' + (`C-h m' with default key bindings) to learn about all the actions + they can perform. + + +[Speed up backlinks’ or query links’ buffer creation?] See section 25.10 + +[Use `denote-grep' to search inside files] See section 15 + +[The backlinks’ buffer] See section 9.11 + +[Add a query link] See section 9.3 + +[Features of the file-naming scheme for searching or filtering] See +section 7.4 + + +17 Minibuffer histories +═══════════════════════ + + Denote has a dedicated minibuffer history for each one of its prompts. + This practically means that using `M-p' (`previous-history-element') + and `M-n' (`next-history-element') will only cycle through the + relevant record of inputs, such as your latest titles in the `TITLE' + prompt, and keywords in the `KEYWORDS' prompt. + + The built-in `savehist' library saves minibuffer histories. Sample + configuration: + + ┌──── + │ (require 'savehist) + │ (setq savehist-file (locate-user-emacs-file "savehist")) + │ (setq history-length 500) + │ (setq history-delete-duplicates t) + │ (setq savehist-save-minibuffer-history t) + │ (add-hook 'after-init-hook #'savehist-mode) + └──── + + +18 Packages that build on Denote +════════════════════════════════ + + This is a list of packages that extend Denote. If you are a package + author, please let us know about your work and we will include it here + (either use the Git repositories or email Protesilaos directly). + + +18.1 Use the `consult-denote' package for enhanced minibuffer interactions +────────────────────────────────────────────────────────────────────────── + + The `consult-denote' package by me (Protesilaos) integrates Denote + with Daniel Mendler’s `consult' package: + . + + The idea is to preserve the familiar patterns of interaction with the + various Denote commands but add to them an extra layer of + functionality, such as the preview mechanism that Consult provides + (e.g. preview the file you are about to link to). + + Additionally, `consult-denote' defines new “sources” for the + `consult-buffer' command. This command provides a single point of + entry for buffers, recently opened files, and bookmarks. With + `consult-denote', it has a dedicated place for Denote-specific + buffers, silos, and more (all of which are configurable). + + Unlike the `consult-notes' package by Colin McLear, `consult-denote' + uses the same presentation of data in the minibuffer to stay in sync + with Denote and make its feature set entirely optional ([Use the + `consult-notes' package]). It also only works with Denote. + + +[Use the `consult-notes' package] See section 18.9 + + +18.2 Use the `denote-sequence' package to write sequence notes or "folgezettel" +─────────────────────────────────────────────────────────────────────────────── + + This section is about the external package `denote-sequence' (by + Protesilaos). The original idea was to include the code as part of the + `denote' package, but we decided to keep each optional extension as a + separate package to make things easier to maintain and to understand. + + Denote defines an optional file name component called the `SIGNATURE' + ([The file-naming scheme]). This is a free-form field that users can + fill in with whatever text they want, such as to have a video split up + into `part1' and `part2', or to set some kind of priority like `a' and + `b', or even to have a special tag that stands out from the rest of + the keywords. + + A more specialised use-case of the `SIGNATURE' is to define a + hierarchical relationship between notes, such that the thoughts they + expound on form sequences. For example, an article about the Labrador + Retriever dog breed is a continuation of a thought process that + extends something about dog breeds in general which, in turn, is a + topic that belongs to the wider theme of dogs. + + The `denote-sequence' package has a manual that explains these + concepts and relevant commands in further detail: + + ⁃ Package name (GNU ELPA): `denote-sequence' + ⁃ Official manual: + ⁃ Git repository: + ⁃ Backronym: Denote… Sequences Efficiently Queue Unsorted Entries + Notwithstanding Curation Efforts. + + +[The file-naming scheme] See section 7 + + +18.3 Use the `denote-markdown' package to better integrate Markdown with Denote +─────────────────────────────────────────────────────────────────────────────── + + The `denote-markdown' package (by Protesilaos) provides some + convenience functions to better integrate Markdown with Deonte. This + is mostly about converting links from one type to another so that they + can work in different applications (because Markdown does not have a + standardised way to define custom link types). + + The code of `denote-markdown' used to be bundled up with the `denote' + package before version `4.0.0' of the latter and was available in the + file `denote-md-extras.el'. Users of the old code will need to adapt + their setup to use the `denote-markdown' package. This can be done by + replacing all instances of `denote-md-extras' with `denote-markdown' + across their configuration. + + ⁃ Package name (GNU ELPA): `denote-markdown' + ⁃ Official manual: + ⁃ Git repository: + ⁃ Backronyms: Denote… Markdown’s Ambitious Reimplimentations Knowingly + Dilute Obvious Widespread Norms; Denote… Markup Agnosticism Requires + Knowhow to Do Only What’s Necessary. + + +18.4 Use the `denote-journal' package which was formerly `denote-journal-extras.el' +─────────────────────────────────────────────────────────────────────────────────── + + The `denote-journal' package (by Protesilaos) makes it easier to use + Denote for journaling. While it is possible to use the generic + `denote' command (and related) to maintain a journal, this package + defines extra functionality to streamline the journaling workflow. + + The code of `denote-journal' used to be bundled up with the `denote' + package before version `4.0.0' of the latter and was available in the + file `denote-journal-extras.el'. Users of the old code will need to + adapt their setup to use the `denote-journal' package. This can be + done by replacing all instances of `denote-journal-extras' with + `denote-journal' across their configuration. + + ⁃ Package name (GNU ELPA): `denote-journal' + ⁃ Official manual: + ⁃ Git repository: + ⁃ Backronym: Denote… Journaling Obviously Utilises Reasonableness + Notwithstanding Affectionate Longing. + + +18.5 Use the `denote-silo' package which formerly was `denote-silo-extras.el' +───────────────────────────────────────────────────────────────────────────── + + The `denote-silo' package (by Protesilaos) provides convenience + functions for working with silos ([Maintain separate directory silos + for notes]). + + The code of `denote-silo' used to be bundled up with the `denote' + package before version `4.0.0' of the latter and was available in the + file `denote-silo-extras.el'. Users of the old code will need to adapt + their setup to use the `denote-silo' package. This can be done by + replacing all instances of `denote-silo-extras' with `denote-silo' + across their configuration. + + ⁃ Package name (GNU ELPA): `denote-silo' + ⁃ Official manual: + ⁃ Git repository: + ⁃ Backronym: Denote… Silos Insulate Localised Objects. + + +[Maintain separate directory silos for notes] See section 5.7 + + +18.6 Use the `denote-search' package as a search interface +────────────────────────────────────────────────────────── + + [ As part of version `4.0.0', Denote comes with the `denote-grep' + command and related functionality ([Use `denote-grep' to search + inside files]). The core of this feature set was written by Lucas + Quintana. ] + + The `denote-search' package by Lucas Quintana provides a search + utility for Denote: . + + It allows you to search for a regular expression in the content of + your notes. Its main advantages over other similar tools are the + possibility of filtering the results by file name and doing further + searches in the files matched previously. This allows for advanced + usage (think about finding a note with two or three specific words in + different lines and with a specific keyword). More features are + described in its comprehensive manual. `denote-search' builds upon + standard Emacs libraries, namely Xref, and so it doesn’t have external + dependencies other than Denote itself. + + +[Use `denote-grep' to search inside files] See section 15 + + +18.7 Use the `denote-explore' package to explore your notes +─────────────────────────────────────────────────────────── + + Peter Prevos has developed the `denote-explore' package which provides + four groups of Emacs commands to explore your Denote files: + + Summary statistics + Count notes, attachments and keywords. + Random walks + Generate new ideas using serendipity. + Janitor + Manage your denote collection. + Visualisations + Visualise your Denote network. + + The package’s documentation covers the details: + . + + +18.8 Use the `citar-denote' package for bibliography notes +────────────────────────────────────────────────────────── + + Peter Prevos has produced the `citar-denote' package which makes it + possible to write notes on BibTeX entries with the help of the `citar' + package. These notes have the citation’s unique key associated with + them in the file’s front matter. They also get a configurable keyword + in their file name, making it easy to find them in Dired and/or + retrieve them with the various Denote methods. + + With `citar-denote', the user leverages standard minibuffer completion + mechanisms (e.g. with the help of the `vertico' and `embark' packages) + to manage bibliographic notes and access those notes with ease. The + package’s documentation covers the details: + . + + +18.9 Use the `consult-notes' package +──────────────────────────────────── + + [ Also check the `consult-denote' package by me (Protesilaos): [Use + the `consult-denote' package for enhanced minibuffer + interactions]. ] + + If you are using Daniel Mendler’s `consult' (which is a brilliant + package), you will most probably like its `consult-notes' extension, + developed by Colin McLear. It uses the familiar mechanisms of Consult + to preview the currently selected entry and to filter searches via a + prefix key. For example: + + ┌──── + │ (setq consult-notes-file-dir-sources + │ `(("Denote Notes" ?d ,(denote-directory)) + │ ("Books" ?b "~/Documents/books/"))) + └──── + + With the above, `M-x consult-notes' will list the files in those two + directories. If you type `d' and space, it narrows the list to just + the notes, while `b' does the same for books. + + The other approach is to enable the `consult-notes-denote-mode'. It + takes care to add the `denote-directory' to the sources that + `consult-notes' reads from. Denote notes are then filtered by the `d' + prefix followed by a space. + + The minor mode has the extra feature of reformatting the title of + notes shown in the minibuffer. It isolates the `TITLE' component of + each note and shows it without hyphens, while presenting keywords in + their own column. The user option `consult-notes-denote-display-id' + can be set to `nil' to hide the identifier. Depending on how one + searches through their notes, this refashioned presentation may be the + best option ([Features of the file-naming scheme for searching or + filtering]). + + +[Use the `consult-denote' package for enhanced minibuffer interactions] +See section 18.1 + +[Features of the file-naming scheme for searching or filtering] See +section 7.4 + + +18.10 Use the `denote-menu' package +─────────────────────────────────── + + Denote’s file-naming scheme is designed to be efficient and to provide + valueable meta information about the file. The cost, however, is that + it is terse and harder to read, depending on how the user chooses to + filter and process their notes. + + To this end, [the `denote-menu' package by Mohamed Suliman] provides + the convenience of a nice tabular interface for all notes. + `denote-menu' removes the delimiters that are found in Denote file + names and presents the information in a human-readable format. + Furthermore, the package provides commands to interact with the list + of notes, such as to filter them and to transition from the tabular + list to Dired. Its documentation expands on the technicalities. + + +[the `denote-menu' package by Mohamed Suliman] + + + +18.11 Use the `denote-zettel-interface' package +─────────────────────────────────────────────── + + The [`denote-zettel-interface' package by Kristoffer Balintona] is + designed for those who want to use Denote while adhering to a strict + Zettelkasten methodology of sequence notes (Folgezettel). This method + leverages the optional `SIGNATURE' file name component of Denote ([The + file-naming scheme]). The package provides a point of entry to one’s + note by visualising them in a tabulated (grid) interface. Files are + sorted by their Folgezettel index. Users can then use a number of + commands to filter their files, navigate around, and the like. + + Note that the package is in early development as of this writing + (2024-12-03 10:18 +0200). + + +[`denote-zettel-interface' package by Kristoffer Balintona] + + +[The file-naming scheme] See section 7 + + +19 Extending Denote +═══════════════════ + + Denote is a tool with a narrow scope: create notes and link between + them, based on the aforementioned file-naming scheme. For other common + operations the user is advised to rely on standard Emacs facilities or + specialised third-party packages ([Packages that build on + Denote]). This section covers the details. + + +[Packages that build on Denote] See section 18 + +19.1 Access the data of the latest note +─────────────────────────────────────── + + The variable `denote-current-data' is updated each time a new note is + created as well as after a rename operation. + + This is an alist where each `car' is one among `title', `keywords', + `signature', `directory', `date', `id', `file-type', `template'. The + value each of them contains is the unprocessed input (e.g. the title + before it is sluggified). + + Users who need to access this data as part of their custom code can + rely on the hooks `denote-after-new-note-hook' and + `denote-after-rename-file-hook'. + + +19.2 Create a new note in any directory +─────────────────────────────────────── + + The commands that create new files are designed to write to the + `denote-directory'. The idea is that the linking mechanism can find + any file by its identifier if it is in the `denote-directory' + (searching the entire file system would be cumbersome). + + However, these are cases where the user needs to create a new note in + an arbitrary directory. The following command can do this. Put the + code in your configuration file and evaluate it. Then call the command + by its name with `M-x'. + + ┌──── + │ (defun my-denote-create-note-in-any-directory () + │ "Create new Denote note in any directory. + │ Prompt for the directory using minibuffer completion." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-directory (read-directory-name "New note in: " nil nil :must-match))) + │ (call-interactively 'denote))) + └──── + + +19.3 Find empty notes and put them in a Dired buffer +──────────────────────────────────────────────────── + + [ This feature is based on the command `denote-sort-dired' ([Sort + files by component]). ] + + Users may have a workflow where they use the commands + `denote-link-or-create' or `denote-link-after-creating' (and related) + to produce new notes that they plan to elaborate on later ([Link to an + existing note or create a new one]). + + To help users find those empty notes, we document the following + commands: + + • `my-denote-sort-dired-empty-files' + • `my-denote-sort-dired-without-empty-files' + • `my-denote-sort-dired-all-empty-files' + • `my-denote-sort-dired-without-all-empty-files' + + ┌──── + │ (require 'denote-sort) + │ + │ (defun my-denote--note-has-no-contents-p (file) + │ "Return non-nil if FILE is an empty note. + │ This means that FILE conforms with `denote-file-is-note-p' and either + │ has no contents or has only the front matter." + │ (and (denote-file-is-note-p file) + │ (or (denote--file-with-temp-buffer file + │ (re-search-forward "^$" nil t) + │ (if (re-search-forward "[^\s\t\n\r]+" nil t) + │ nil + │ t)) + │ ;; This must come later because here we consider a file + │ ;; "empty" even if it only has front matter. + │ (denote--file-empty-p file)))) + │ + │ (defun my-denote-sort-dired-empty-files (files-matching-regexp sort-by-component reverse) + │ "Like `denote-sort-dired' but only cover empty files. + │ Empty files are those that satisfy `my-denote--note-has-no-contents-p'." + │ (interactive + │ (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts))) + │ (let ((component (or sort-by-component + │ denote-sort-dired-default-sort-component + │ 'identifier)) + │ (reverse-sort (or reverse + │ denote-sort-dired-default-reverse-sort + │ nil))) + │ (if-let* ((default-directory (denote-directory)) + │ (files (denote-sort-get-directory-files files-matching-regexp component reverse-sort)) + │ (empty-files (seq-filter #'my-denote--note-has-no-contents-p files)) + │ ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as + │ ;; buffer-name produces an error if the regexp contains a + │ ;; wildcard for a directory. I can reproduce this in emacs + │ ;; -Q and am not sure if it is a bug. Anyway, I will report + │ ;; it upstream, but even if it is fixed we cannot use it + │ ;; for now (whatever fix will be available for Emacs 30+). + │ (denote-sort-dired-buffer-name (format "Denote sort `%s' by `%s'" files-matching-regexp component)) + │ (buffer-name (format "Denote sort by `%s' at %s" component (format-time-string "%T")))) + │ (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name empty-files))))) + │ (setq denote-sort--dired-buffer dired-buffer) + │ (with-current-buffer dired-buffer + │ (setq-local revert-buffer-function + │ (lambda (&rest _) + │ (kill-buffer dired-buffer) + │ (denote-sort-dired files-matching-regexp component reverse-sort)))) + │ ;; Because of the above NOTE, I am printing a message. Not + │ ;; what I want, but it is better than nothing... + │ (message denote-sort-dired-buffer-name)) + │ (message "No matching files for: %s" files-matching-regexp)))) + │ + │ (defun my-denote-sort-dired-without-empty-files (files-matching-regexp sort-by-component reverse) + │ "Like `denote-sort-dired' but only cover empty files. + │ Empty files are those that satisfy `my-denote--note-has-no-contents-p'." + │ (interactive + │ (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts))) + │ (let ((component (or sort-by-component + │ denote-sort-dired-default-sort-component + │ 'identifier)) + │ (reverse-sort (or reverse + │ denote-sort-dired-default-reverse-sort + │ nil))) + │ (if-let* ((default-directory (denote-directory)) + │ (files (denote-sort-get-directory-files files-matching-regexp component reverse-sort)) + │ (empty-files (seq-remove #'my-denote--note-has-no-contents-p files)) + │ ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as + │ ;; buffer-name produces an error if the regexp contains a + │ ;; wildcard for a directory. I can reproduce this in emacs + │ ;; -Q and am not sure if it is a bug. Anyway, I will report + │ ;; it upstream, but even if it is fixed we cannot use it + │ ;; for now (whatever fix will be available for Emacs 30+). + │ (denote-sort-dired-buffer-name (format "Denote sort `%s' by `%s'" files-matching-regexp component)) + │ (buffer-name (format "Denote sort by `%s' at %s" component (format-time-string "%T")))) + │ (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name empty-files))))) + │ (setq denote-sort--dired-buffer dired-buffer) + │ (with-current-buffer dired-buffer + │ (setq-local revert-buffer-function + │ (lambda (&rest _) + │ (kill-buffer dired-buffer) + │ (denote-sort-dired files-matching-regexp component reverse-sort)))) + │ ;; Because of the above NOTE, I am printing a message. Not + │ ;; what I want, but it is better than nothing... + │ (message denote-sort-dired-buffer-name)) + │ (message "No matching files for: %s" files-matching-regexp)))) + │ + │ (defun my-denote-sort-dired-all-empty-files () + │ "List all empty files in a Dired buffer. + │ This is the same as calling `my-denote-sort-dired' with a + │ FILES-MATCHING-REGEXP of \".*\"." + │ (declare (interactive-only t)) + │ (interactive) + │ (let* ((other-prompts (denote-sort-dired--prompts)) + │ (sort-key (nth 1 other-prompts)) + │ (reverse (nth 2 other-prompts))) + │ (funcall-interactively #'my-denote-sort-dired-empty-files ".*" sort-key reverse))) + │ + │ (defun my-denote-sort-dired-without-all-empty-files () + │ "List all empty files in a Dired buffer. + │ This is the same as calling `my-denote-sort-dired' with a + │ FILES-MATCHING-REGEXP of \".*\"." + │ (declare (interactive-only t)) + │ (interactive) + │ (let* ((other-prompts (denote-sort-dired--prompts)) + │ (sort-key (nth 1 other-prompts)) + │ (reverse (nth 2 other-prompts))) + │ (funcall-interactively #'my-denote-sort-dired-without-empty-files ".*" sort-key reverse))) + └──── + + [ In the above snippet, I am purposefully duplicating code to make it + easier for users to pick the ones they need. ] + + +[Sort files by component] See section 14 + +[Link to an existing note or create a new one] See section 9.10 + + +19.4 Automatically rename the note after saving it +────────────────────────────────────────────────── + + While experimenting with Denote, users may need to try different + workflows to figure out what works for them. Those might involve + changing keywords and specifying titles in a particular way. The + following sample can be used: + + ┌──── + │ (defun my-denote-always-rename-on-save-based-on-front-matter () + │ "Rename the current Denote file, if needed, upon saving the file. + │ Rename the file based on its front matter, checking for changes in the + │ title or keywords fields. + │ + │ Add this function to the `after-save-hook'." + │ (let ((denote-rename-confirmations nil) + │ (denote-save-buffers t)) ; to save again post-rename + │ (when (and buffer-file-name (denote-file-is-note-p buffer-file-name)) + │ (ignore-errors (denote-rename-file-using-front-matter buffer-file-name)) + │ (message "Buffer saved; Denote file renamed")))) + │ + │ (add-hook 'after-save-hook #'my-denote-always-rename-on-save-based-on-front-matter) + └──── + + +19.5 Narrow the list of files in Dired +────────────────────────────────────── + + Emacs’ standard file manager (or directory editor) can read a regular + expression to mark the matching files. This is the command + `dired-mark-files-regexp', which is bound to `% m' by default. For + example, `% m _denote' will match all files that have the `denote' + keyword ([Features of the file-naming scheme for searching or + filtering]). + + Once the files are matched, the user has two options: (i) narrow the + list to the matching items or (ii) exclude the matching items from the + list. + + For the former, we want to toggle the marks by typing `t' (calls the + command `dired-toggle-marks' by default) and then hit the letter `k' + (for `dired-do-kill-lines'). The remaining files are those that match + the regexp that was provided earlier. + + For the latter approach of filtering out the matching items, simply + involves the use of the `k' command (`dired-do-kill-lines') to omit + the marked files from the list. + + These sequences can be combined to incrementally narrow the list. + Note that `dired-do-kill-lines' does not delete files: it simply hides + them from the current view. + + Revert to the original listing with `g' (`revert-buffer'). + + For a convenient wrapper, consider this example: + + ┌──── + │ (defvar prot-dired--limit-hist '() + │ "Minibuffer history for `prot-dired-limit-regexp'.") + │ + │ ;;;###autoload + │ (defun prot-dired-limit-regexp (regexp omit) + │ "Limit Dired to keep files matching REGEXP. + │ + │ With optional OMIT argument as a prefix (\\[universal-argument]), + │ exclude files matching REGEXP. + │ + │ Restore the buffer with \\`\\[revert-buffer]'." + │ (interactive + │ (list + │ (read-regexp + │ (concat "Files " + │ (when current-prefix-arg + │ (propertize "NOT " 'face 'warning)) + │ "matching PATTERN: ") + │ nil 'prot-dired--limit-hist) + │ current-prefix-arg)) + │ (dired-mark-files-regexp regexp) + │ (unless omit (dired-toggle-marks)) + │ (dired-do-kill-lines)) + └──── + + +[Features of the file-naming scheme for searching or filtering] See +section 7.4 + + +19.6 Use `dired-virtual-mode' for arbitrary file listings +───────────────────────────────────────────────────────── + + Emacs’ Dired is a powerful file manager that builds its functionality + on top of the Unix `ls' command. As noted elsewhere in this manual, + the user can update the `ls' flags that Dired uses to display its + contents ([I want to sort by last modified, why won’t Denote let + me?]). + + What Dired cannot do is parse the output of a result that is produced + by piped commands, such as `ls -l | sort -t _ -k2'. This specific + example targets the second underscore-separated field of the file + name, per our conventions ([The file-naming scheme]). Conceretely, it + matches the “alpha” as the sorting key in something like this: + + ┌──── + │ 20220929T200432--testing-file-one__alpha.txt + └──── + + Consider then, how Dired will sort those files by their identifier: + + ┌──── + │ 20220929T200432--testing-file-one__alpha.txt + │ 20220929T200532--testing-file-two__beta.txt + │ 20220929T200632--testing-file-three__alpha.txt + │ 20220929T200732--testing-file-four__beta.txt + └──── + + Whereas on the command line, we can get the following: + + ┌──── + │ $ ls | sort -t _ -k 2 + │ 20220929T200432--testing-file-one__alpha.txt + │ 20220929T200632--testing-file-three__alpha.txt + │ 20220929T200532--testing-file-two__beta.txt + │ 20220929T200732--testing-file-four__beta.txt + └──── + + This is where `dired-virtual-mode' shows its utility. If we tweak our + command-line invocation to include `ls -l', this mode can behave like + Dired on the listed files. (We omit the output of the `-l' flag from + this tutorial, as it is too verbose.) + + What we now need is to capture the output of `ls -l | sort -t _ -k 2' + in an Emacs buffer and then enable `dired-virtual-mode'. To do that, + we can rely on either `M-x shell' or `M-x eshell' and then manually + copy the relevant contents. + + For the user’s convenience, I share what I have for Eshell to quickly + capture the last command’s output in a dedicated buffer: + + ┌──── + │ (defcustom prot-eshell-output-buffer "*Exported Eshell output*" + │ "Name of buffer with the last output of Eshell command. + │ Used by `prot-eshell-export'." + │ :type 'string + │ :group 'prot-eshell) + │ + │ (defcustom prot-eshell-output-delimiter "* * *" + │ "Delimiter for successive `prot-eshell-export' outputs. + │ This is formatted internally to have newline characters before + │ and after it." + │ :type 'string + │ :group 'prot-eshell) + │ + │ (defun prot-eshell--command-prompt-output () + │ "Capture last command prompt and its output." + │ (let ((beg (save-excursion + │ (goto-char (eshell-beginning-of-input)) + │ (goto-char (point-at-bol))))) + │ (when (derived-mode-p 'eshell-mode) + │ (buffer-substring-no-properties beg (eshell-end-of-output))))) + │ + │ ;;;###autoload + │ (defun prot-eshell-export () + │ "Produce a buffer with output of the last Eshell command. + │ If `prot-eshell-output-buffer' does not exist, create it. Else + │ append to it, while separating multiple outputs with + │ `prot-eshell-output-delimiter'." + │ (interactive) + │ (let ((eshell-output (prot-eshell--command-prompt-output))) + │ (with-current-buffer (get-buffer-create prot-eshell-output-buffer) + │ (let ((inhibit-read-only t)) + │ (goto-char (point-max)) + │ (unless (eq (point-min) (point-max)) + │ (insert (format "\n%s\n\n" prot-eshell-output-delimiter))) + │ (goto-char (point-at-bol)) + │ (insert eshell-output) + │ (switch-to-buffer-other-window (current-buffer)))))) + └──── + + Bind `prot-eshell-export' to a key in the `eshell-mode-map' and give + it a try (I use `C-c C-e'). In the produced buffer, activate the + `dired-virtual-mode'. + + +[I want to sort by last modified, why won’t Denote let me?] See section +25.7 + +[The file-naming scheme] See section 7 + + +19.7 Use Embark to collect minibuffer candidates +──────────────────────────────────────────────── + + `embark' is a remarkable package that lets you perform relevant, + context-dependent actions using a prefix key (simplifying in the + interest of brevity). + + For our purposes, Embark can be used to produce a Dired listing + directly from the minibuffer. Suppose the current note has links to + three other notes. You might use the `denote-find-link' command to + pick one via the minibuffer. But why not turn those three links into + their own Dired listing? While in the minibuffer, invoke `embark-act' + which you may have already bound to `C-.' and then follow it up with + `E' (for the `embark-export' command). + + This pattern can be repeated with any list of candidates, meaning that + you can narrow the list by providing some input before eventually + exporting the results with Embark. + + Overall, this is very powerful and you might prefer it over doing the + same thing directly in Dired, since you also benefit from all the + power of the minibuffer ([Narrow the list of files in Dired]). + + +[Narrow the list of files in Dired] See section 19.5 + + +19.8 Search file contents +───────────────────────── + + [ Users of `consult' can use the `consult-denote' package instead + ([Use the `consult-denote' package for enhanced minibuffer + interactions]). ] + + Emacs provides built-in commands which are wrappers of standard Unix + tools: `M-x grep' lets the user input the flags of a `grep' call and + pass a regular expression to the `-e' flag. + + The author of Denote uses this thin wrapper instead: + + ┌──── + │ (defvar prot-search--grep-hist '() + │ "Input history of grep searches.") + │ + │ ;;;###autoload + │ (defun prot-search-grep (regexp &optional recursive) + │ "Run grep for REGEXP. + │ + │ Search in the current directory using `lgrep'. With optional + │ prefix argument (\\[universal-argument]) for RECURSIVE, run a + │ search starting from the current directory with `rgrep'." + │ (interactive + │ (list + │ (read-from-minibuffer (concat (if current-prefix-arg + │ (propertize "Recursive" 'face 'warning) + │ "Local") + │ " grep for PATTERN: ") + │ nil nil nil 'prot-search--grep-hist) + │ current-prefix-arg)) + │ (unless grep-command + │ (grep-compute-defaults)) + │ (if recursive + │ (rgrep regexp "*" default-directory) + │ (lgrep regexp "*" default-directory))) + └──── + + Rather than maintain custom code, consider using the excellent + `consult' package: it provides commands such as `consult-grep' and + `consult-find' which provide live results and are generally easier to + use than the built-in commands. + + +[Use the `consult-denote' package for enhanced minibuffer interactions] +See section 18.1 + + +19.9 Bookmark the directory with the notes +────────────────────────────────────────── + + Part of the reason Denote does not reinvent existing functionality is + to encourage you to learn more about Emacs. You do not need a bespoke + “jump to my notes” directory because such commands do not scale well. + Will you have a “jump to my downloads” then another for multimedia and + so on? No. + + Emacs has a built-in framework for recording persistent markers to + locations. Visit the `denote-directory' (or any dir/file for that + matter) and invoke the `bookmark-set' command (bound to `C-x r m' by + default). It lets you create a bookmark. + + The list of bookmarks can be reviewed with the `bookmark-bmenu-list' + command (bound to `C-x r l' by default). A minibuffer interface is + available with `bookmark-jump' (`C-x r b'). + + If you use the `consult' package, its default `consult-buffer' command + has the means to group together buffers, recent files, and bookmarks. + Each of those types can be narrowed to with a prefix key. The package + `consult-dir' is an extension to `consult' which provides useful + extras for working with directories, including bookmarks. + + +19.10 Treat your notes as a project +─────────────────────────────────── + + Emacs has a built-in library for treating a directory tree as a + “project”. This means that the contents of this tree are seen as part + of the same set, so commands like `project-switch-to-buffer' (`C-x p + b' by default) will only consider buffers in the current project + (e.g. three notes that are currently being visited). + + Normally, a “project” is a directory tree whose root is under version + control. For our purposes, all you need is to navigate to the + `denote-directory' (for the shell or via Dired) and use the + command-line to run this (requires the `git' executable): + + ┌──── + │ git init + └──── + + + From Dired, you can type `M-!' which invokes + `dired-smart-shell-command' and then run the git call there. + + The project can then be registered by invoking any project-related + command inside of it, such as `project-find-file' (`C-x p f'). + + It is a good idea to keep your notes under version control, as that + gives you a history of changes for each file. We shall not delve into + the technicalities here, though suffice to note that Emacs’ built-in + version control framework or the exceptionally well-crafted `magit' + package will get the job done (VC can work with other backends besides + Git). + + +19.11 Use the tree-based file prompt for select commands +──────────────────────────────────────────────────────── + + Older versions of Denote had a file prompt that resembled that of the + standard `find-file' command (bound to `C-x C-f' by default). This + means that it used a tree-based method of navigating the filesystem by + selecting the specific directory and then the given file. + + Currently, Denote flattens the file prompt so that every file in the + `denote-directory' and its subdirectories can be matched from anywhere + using the power of Emacs’ minibuffer completion (such as with the help + of the `orderless' package in addition to built-in options). + + Users who need the old behaviour on a per-command basis can define + their own wrapper functions as shown in the following code block. + + ┌──── + │ ;; This is the old `denote-file-prompt' that we renamed to + │ ;; `denote-file-prompt-original' for clarity. + │ (defun denote-file-prompt-original (&optional initial-text) + │ "Prompt for file with identifier in variable `denote-directory'. + │ With optional INITIAL-TEXT, use it to prepopulate the minibuffer." + │ (read-file-name "Select note: " (denote-directory) nil nil initial-text + │ (lambda (f) + │ (or (denote-file-has-identifier-p f) + │ (file-directory-p f))))) + │ + │ ;; Our wrapper command that changes the current `denote-file-prompt' + │ ;; to the functionality of `denote-file-prompt-original' only when + │ ;; this command is used. + │ (defun my-denote-link () + │ "Call `denote-link' but use Denote's original file prompt. + │ See `denote-file-prompt-original'." + │ (interactive) + │ (cl-letf (((symbol-function 'denote-file-prompt) #'denote-file-prompt-original)) + │ (call-interactively #'denote-link))) + └──── + + +19.12 Rename files with Denote in the Image Dired thumbnails buffer +─────────────────────────────────────────────────────────────────── + + [Rename files with Denote using `dired-preview'] + + Just as with the `denote-dired-rename-marked-files-with-keywords', we + can use Denote in the Image Dired buffer ([Rename multiple files at + once]). Here is the custom code: + + ┌──── + │ (autoload 'image-dired--with-marked "image-dired") + │ (autoload 'image-dired-original-file-name "image-dired-util") + │ + │ (defun my-denote-image-dired-rename-marked-files (keywords) + │ "Like `denote-dired-rename-marked-files-with-keywords' but for Image Dired. + │ Prompt for KEYWORDS and rename all marked files in the Image + │ Dired buffer to have a Denote-style file name with the given + │ KEYWORDS. + │ + │ IMPORTANT NOTE: if there are marked files in the corresponding + │ Dired buffers, those will be targeted as well. This is not the + │ fault of Denote: it is how Dired and Image Dired work in tandem. + │ To only rename the marked thumbnails, start by unmarking + │ everything in Dired. Then mark the items in Image Dired and + │ invoke this command." + │ (interactive (list (denote-keywords-prompt)) image-dired-thumbnail-mode) + │ (image-dired--with-marked + │ (when-let* ((file (image-dired-original-file-name)) + │ (dir (file-name-directory file)) + │ (id (or (denote-retrieve-filename-identifier file) "")) + │ (file-type (denote-filetype-heuristics file)) + │ (title (denote--retrieve-title-or-filename file file-type)) + │ (signature (or (denote-retrieve-filename-signature file) "") + │ (extension (file-name-extension file t)) + │ (new-name (denote-format-file-name dir id keywords title extension signature)) + │ (default-directory dir)) + │ (denote-rename-file-and-buffer file new-name)))) + └──── + + While the `my-denote-image-dired-rename-marked-files' renames files in + the helpful Denote-compliant way, users may still need to not prepend + a unique identifier and not sluggify (hyphenate and downcase) the + image’s existing file name. To this end, the following custom command + can be used instead: + + ┌──── + │ (defun my-image-dired-rename-marked-files (keywords) + │ "Like `denote-dired-rename-marked-files-with-keywords' but for Image Dired. + │ Prompt for keywords and rename all marked files in the Image + │ Dired buffer to have Denote-style keywords, but none of the other + │ conventions of Denote's file-naming scheme." + │ (interactive (list (denote-keywords-prompt)) image-dired-thumbnail-mode) + │ (image-dired--with-marked + │ (when-let* ((file (image-dired-original-file-name)) + │ (dir (file-name-directory file)) + │ (file-type (denote-filetype-heuristics file)) + │ (title (denote--retrieve-title-or-filename file file-type)) + │ (extension (file-name-extension file t)) + │ (kws (denote--keywords-combine keywords)) + │ (new-name (concat dir title "__" kws extension)) + │ (default-directory dir)) + │ (denote-rename-file-and-buffer file new-name)))) + └──── + + +[Rename files with Denote using `dired-preview'] See section 19.13 + +[Rename multiple files at once] See section 6.3 + + +19.13 Rename files with Denote using `dired-preview' +──────────────────────────────────────────────────── + + The `dired-preview' package (by me/Protesilaos) automatically displays + a preview of the file at point in Dired. This can be helpful in + tandem with Denote when we want to rename multiple files by taking a + quick look at their contents. + + The command `denote-dired-rename-marked-files-with-keywords' will + generate Denote-style file names based on the keywords it prompts + for. Identifiers are derived from each file’s modification date + ([Rename multiple files at once]). There is no need for any custom + code in this scenario. + + As noted in the section about Image Dired, the user may sometimes not + need a fully fledged Denote-style file name but only append + Denote-like keywords to each file name (e.g. `Original + Name__denote_test.jpg' instead of + `20230710T195843--original-name__denote_test.jpg'). + + [Rename files with Denote in the Image Dired thumbnails buffer] + + In such a workflow, it is unlikely to be dealing with ordinary text + files where front matter can be helpful. A custom command does not + need to behave like what Denote provides out-of-the-box, but can + instead append keywords to file names without conducting any further + actions. We thus have: + + ┌──── + │ (defun my-denote-dired-rename-marked-files-keywords-only () + │ "Like `denote-dired-rename-marked-files-with-keywords' but only for keywords in file names. + │ + │ Prompt for keywords and rename all marked files in the Dired + │ buffer to only have Denote-style keywords, but none of the other + │ conventions of Denote's file-naming scheme." + │ (interactive nil dired-mode) + │ (if-let* ((marks (dired-get-marked-files))) + │ (let ((keywords (denote-keywords-prompt))) + │ (dolist (file marks) + │ (let* ((dir (file-name-directory file)) + │ (file-type (denote-filetype-heuristics file)) + │ (title (denote--retrieve-title-or-filename file file-type)) + │ (extension (file-name-extension file t)) + │ (kws (denote--keywords-combine keywords)) + │ (new-name (concat dir title "__" kws extension))) + │ (denote-rename-file-and-buffer file new-name))) + │ (revert-buffer)) + │ (user-error "No marked files; aborting"))) + └──── + + +[Rename multiple files at once] See section 6.3 + +[Rename files with Denote in the Image Dired thumbnails buffer] See +section 19.12 + + +19.14 Avoid duplicate identifiers when exporting Denote notes +───────────────────────────────────────────────────────────── + + When exporting Denote notes to, for example, an HTML or PDF file, + there is a high probability that the same file name is used with a new + extension. This is problematic because it creates files with + duplicate identifiers. The `20230515T085612--example__keyword.org' + produces a `20230515T085612--example__keyword.pdf'. Any link to the + `20230515T085612' will thus break: it does not honor Denote’s + expectation of finding unique identifiers. This is not the fault of + Denote: exporting is done by the user without Denote’s involvement. + + Org Mode and Markdown use different approaches to exporting files. No + recommended method is available for plain text files as there is no + standardised export functionality for this format (the user can always + create a new note using the file type they want on a case-by-case + basis: [Convenience commands for note creation]). + + +[Convenience commands for note creation] See section 5.1.4 + +19.14.1 Export Denote notes with Org Mode +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Org Mode has a built-in configurable export engine. You can prevent + duplicate identifiers when exporting manually for each exported file + or by advising the Org export function. + + The `denote-org' package (by Protesilaos) also provides commands to + convert `denote:' links to their `file:' equivalent, in case this is a + required pre-processing step for export purposes. + + +◊ 19.14.1.1 Manually configure Org export + + Insert `#+export_file_name: FILENAME' in the front matter before + exporting to force a filename called whatever the value of `FILENAME' + is. The `FILENAME' does not specify the file type extension, such as + `.pdf'. This is up to the export engine. For example, a Denote note + with a complete file name of `20230515T085612--example__keyword.org' + and a front matter entry of `#+export_file_name: hello' will be + exported as `hello.pdf'. + + The advantage of this manual method is that it gives the user full + control over the resulting file name. The disadvantage is that it + depends on the user’s behaviour. Forgetting to add a new name can + lead to duplicate identifiers, as already noted in the introduction to + this section ([Export Denote notes]). + + + [Export Denote notes] See section 19.14 + + +◊ 19.14.1.2 Automatically store Org exports in another folder + + It is possible to automatically place all exports in another folder by + making Org’s function `org-export-output-file-name' create the target + directory if needed and move the exported file there. Remember that + advising Elisp code must be handled with care, as it might break the + original function in subtle ways. + + ┌──── + │ (defvar my-org-export-output-directory-prefix "./export_" + │ "Prefix of directory used for org-mode export. + │ + │ The single dot means that the directory is created on the same + │ level as the one where the Org file that performs the exporting + │ is. Use two dots to place the directory on a level above the + │ current one. + │ + │ If this directory is part of `denote-directory', make sure it is + │ not read by Denote. See `denote-excluded-directories-regexp'. + │ This way there will be no known duplicate Denote identifiers + │ produced by the Org export mechanism.") + │ + │ (defun my-org-export-create-directory (fn extension &rest args) + │ "Move Org export file to its appropriate directory. + │ + │ Append the file type EXTENSION of the exported file to + │ `my-org-export-output-directory-prefix' and, if absent, create a + │ directory named accordingly. + │ + │ Install this as advice around `org-export-output-file-name'. The + │ EXTENSION is supplied by that function. ARGS are its remaining + │ arguments." + │ (let ((export-dir (format "%s%s" my-org-export-output-directory-prefix extension))) + │ (unless (file-directory-p export-dir) + │ (make-directory export-dir))) + │ (apply fn extension args)) + │ + │ (advice-add #'org-export-output-file-name :around #'my-org-export-create-directory) + └──── + + The target export directory should not be a subdirectory of + `denote-directory', as that will result in duplicate identifiers. + Exclude it with the `denote-excluded-directories-regexp' user option + ([Exclude certain directories from all operations]). + + [ NOTE: I (Protesilaos) am not a LaTeX user and cannot test the + following. ] + + Using a different directory will require some additional configuration + when exporting using LaTeX. The export folder cannot be inside the + path of the `denote-directory' to prevent Denote from recognising it + as an attachment: + . + + + [Exclude certain directories from all operations] See section 5.9 + + +◊ 19.14.1.3 Org Mode Publishing + + Org Mode also has a publishing tool for exporting a collection of + files. Some user might apply this approach to convert their note + collection to a public or private website. + + The `org-publish-project-alist' variable drives the publishing + process, including the publishing directory. + + The publishing directory should not be a subdirectory of + `denote-directory', as that will result in duplicate identifiers. + Exclude it with the `denote-excluded-directories-regexp' user option + ([Exclude certain directories from all operations]). + + + [Exclude certain directories from all operations] See section 5.9 + + +19.14.2 Export Denote notes with Markdown +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Exporting from Markdown requires an external processor (e.g., + Markdown.pl, Pandoc, or MultiMarkdown). The `markdown-command' + variable defines the command line used in export, for example: + + ┌──── + │ (setq markdown-command "multimarkdown") + └──── + + The export process thus occurs outside of Emacs. Users need to read + the documentation of their preferred processor to prevent the creation + of duplicate Denote identifiers. + + +19.15 Set up your workflow for daily or weekly meeting notes +──────────────────────────────────────────────────────────── + + Perhaps as part of work, we meet with certain people on a regular + basis. During the meeting we may discuss a variety of topics. How best + to approach with the help of Denote? + + One option is to write a new file for each meeting, giving it the + appropriate keywords each time ([Points of entry]). This is what + Denote does by default and does not need any further tweaks. If we + need to review those notes, we can use the command `denote-sort-dired' + ([Sort files by component]), or one of the Org dynamic blocks we + provide ([Use Org dynamic blocks]), among other options. + + Another approach is to write one file per person with the regular + `denote' command (or related), give it the name of the person as a + title and, optionally, use some relevant keywords. Inside each file, + write a top-level heading with the date of the meeting, and then + produce the meeting notes below as paragraphs and subheadings. This + can all be done without any changes to Denote, though we can + streamline it by incorporating the following code in our setup. + Configure `my-denote-colleagues' and then use the command + `my-denote-colleagues-new-meeting' to see how it works. + + ┌──── + │ (defvar my-denote-colleagues '("Prot" "Protesilaos") + │ "List of names I collaborate with. + │ There is at least one file in the variable `denote-directory' that has + │ the name of this person.") + │ + │ (defvar my-denote-colleagues-prompt-history nil + │ "Minibuffer history for `my-denote-colleagues-new-meeting'.") + │ + │ (defun my-denote-colleagues-prompt () + │ "Prompt with completion for a name among `my-denote-colleagues'. + │ Use the last input as the default value." + │ (let ((default-value (car my-denote-colleagues-prompt-history))) + │ (completing-read + │ (format-prompt "New meeting with COLLEAGUE" default-value) + │ my-denote-colleagues + │ nil :require-match nil + │ 'my-denote-colleagues-prompt-history + │ default-value))) + │ + │ (defun my-denote-colleagues-get-file (name) + │ "Find file in variable `denote-directory' for NAME colleague. + │ If there are more than one files, prompt with completion for one among + │ them. + │ + │ NAME is one among `my-denote-colleagues'." + │ (if-let* ((files (denote-directory-files name)) + │ (length-of-files (length files))) + │ (cond + │ ((= length-of-files 1) + │ (car files)) + │ ((> length-of-files 1) + │ (completing-read "Select a file: " files nil :require-match))) + │ (user-error "No files for colleague with name `%s'" name))) + │ + │ (defun my-denote-colleagues-new-meeting () + │ "Prompt for the name of a colleague and insert a timestamped heading therein. + │ The name of a colleague corresponds to at least one file in the variable + │ `denote-directory'. In case there are multiple files, prompt to choose + │ one among them and operate therein." + │ (declare (interactive-only t)) + │ (interactive) + │ (let* ((name (my-denote-colleagues-prompt)) + │ (file (my-denote-colleagues-get-file name)) + │ (time (format-time-string "%F %a %R"))) ; remove %R if you do not want the time + │ (with-current-buffer (find-file file) + │ (goto-char (point-max)) + │ ;; Here I am assuming we are in `org-mode', hence the leading + │ ;; asterisk for the heading. Adapt accordingly. + │ (insert (format "* [%s]\n\n" time))))) + └──── + + +[Points of entry] See section 5 + +[Sort files by component] See section 14 + +[Use Org dynamic blocks] See section 13 + + +20 For developers or advanced users +═══════════════════════════════════ + + Denote is in a stable state and can be relied upon as the basis for + custom extensions ([Packages that build on Denote]). Further below is + a list with the functions or variables we provide for public usage. + Those are in addition to all user options and commands that are + already documented in the various sections of this manual. + + In this context “public” is any form with single hyphens in its + symbol, such as `denote-directory-files'. We expressly support those, + meaning that we consider them reliable and commit to documenting any + changes in their particularities (such as through `make-obsolete', a + record in the change log, a blog post on the maintainer’s website, and + the like). + + By contradistinction, a “private” form is declared with two hyphens in + its symbol such as `denote--file-extension'. Do not use those as we + might change them without further notice. + + The following sections cover the specifics. + + +[Packages that build on Denote] See section 18 + +20.1 Common building blocks for developers or advanced users +──────────────────────────────────────────────────────────── + + Variable `denote-id-format' + Format of ID prefix of a note’s filename. The note’s ID is + derived from the date and time of its creation ([The file-naming + scheme]). + + Variable `denote-id-regexp' + Regular expression to match `denote-id-format'. + + Variable `denote-signature-regexp' + Regular expression to match the `SIGNATURE' field in a file + name. + + Variable `denote-title-regexp' + Regular expression to match the `TITLE' field in a file name + ([The file-naming scheme]). + + Variable `denote-keywords-regexp' + Regular expression to match the `KEYWORDS' field in a file name + ([The file-naming scheme]). + + Function `denote-identifier-p' + Return non-nil if `IDENTIFIER' string is a Denote identifier. + + Function `denote-file-is-note-p' + Return non-nil if `FILE' is an actual Denote note. For our + purposes, a note must satisfy `file-regular-p' and + `denote-filename-is-note-p'. + + Function `denote-file-has-identifier-p' + Return non-nil if `FILE' has a Denote identifier. + + Function `denote-file-has-denoted-filename-p' + Return non-nil if `FILE' respects the file-naming scheme of + Denote. This tests the rules of Denote’s file-naming + scheme. Sluggification is ignored. It is done by removing all + file name components and validating what remains. + + Function `denote-file-has-signature-p' + Return non-nil if `FILE' has a signature. + + Function `denote-file-has-supported-extension-p' + Return non-nil if `FILE' has supported extension. Also account + for the possibility of an added `.gpg' suffix. Supported + extensions are those implied by `denote-file-type'. + + Function `denote-file-is-writable-and-supported-p' + Return non-nil if `FILE' is writable and has supported + extension. + + Function `denote-file-type-extensions' + Return all file type extensions in `denote-file-types'. + + Variable `denote-encryption-file-extensions' + List of strings specifying file extensions for encryption. + + Function `denote-file-type-extensions-with-encryption' + Derive `denote-file-type-extensions' plus + `denote-encryption-file-extensions'. + + Function `denote-get-file-extension' + Return extension of `FILE' with dot included. Account for + `denote-encryption-file-extensions'. In other words, return + something like `.org.gpg' if it is part of the file, else return + `.org'. + + Function `denote-get-file-extension-sans-encryption' + Return extension of `FILE' with dot included and without the + encryption part. Build on top of `denote-get-file-extension' + though always return something like `.org' even if the actual + file extension is `.org.gpg'. + + Functions `denote-infer-keywords-from-files' + Return list of keywords in `denote-directory-files'. With + optional `FILES-MATCHING-REGEXP', only extract keywords from the + matching files. Otherwise, do it for all files. Keep any + duplicates. Users who do not want duplicates should refer to the + functions `denote-keywords'. + + Function `denote-keywords' + Return appropriate list of keyword candidates. If + `denote-infer-keywords' is non-nil, infer keywords from existing + notes and combine them into a list with + `denote-known-keywords'. Else use only the latter set of + keywords ([Standard note creation]). In the case of keyword + inferrence, use optional `FILES-MATCHING-REGEXP', to extract + keywords only from the matching files. Otherwise, do it for all + files. Filter inferred keywords with the user option + `denote-excluded-keywords-regexp'. + + Function `denote-keywords-sort' + Sort `KEYWORDS' if `denote-sort-keywords' is non-nil. + `KEYWORDS' is a list of strings, per `denote-keywords-prompt'. + + Function `denote-keywords-combine' + Combine `KEYWORDS' list of strings into a single + string. Keywords are separated by the underscore character, per + the Denote file-naming scheme. + + Function `denote-valid-date-p' + Return `DATE' as a valid date. A valid `DATE' is a value that + can be parsed by either `decode-time' or `date-to-time' .Those + functions signal an error if `DATE' is a value they do not + recognise. If `DATE' is nil, return nil. + + Function `denote-directory' + Return path of the variable `denote-directory' as a proper + directory, also because it accepts a directory-local value for + what we internally refer to as “silos” ([Maintain separate + directories for notes]). Custom Lisp code can `let' bind the + value of the variable `denote-directory' to override what this + function returns. + + Function `denote-directory-files' + Return list of absolute file paths in variable + `denote-directory'. Files that match + `denote-excluded-files-regexp' are excluded from the list. Files + only need to have an identifier. The return value may thus + include file types that are not implied by + `denote-file-type'. With optional `FILES-MATCHING-REGEXP', + restrict files to those matching the given regular + expression. With optional `OMIT-CURRENT' as a non-nil value, do + not include the current Denote file in the returned list. With + optional `TEXT-ONLY' as a non-nil value, limit the results to + text files that satisfy `denote-file-is-note-p'. With optional + `EXCLUDE-REGEXP' exclude the files that match the given regular + expression. This is done after `FILES-MATCHING-REGEXP' and + `OMIT-CURRENT' have been applied. + + Function `denote-directory-subdirectories' + Return list of subdirectories in variable + `denote-directory'. Omit dotfiles (such as .git) + unconditionally. Also exclude whatever matches + `denote-excluded-directories-regexp'. Note that the + `denote-directory' accepts a directory-local value for what we + call “silos” ([Maintain separate directories for notes]). + + +[The file-naming scheme] See section 7 + +[Standard note creation] See section 5.1 + +[Maintain separate directories for notes] See section 5.7 + + +20.2 File path interface for developers or advanced users +───────────────────────────────────────────────────────── + + Function `denote-file-name-relative-to-denote-directory' + Return name of `FILE' relative to the variable + `denote-directory'. `FILE' must be an absolute path. + + Function `denote-slug-keep-only-ascii' + Remove all non-ASCII characters from `STR' and replace them with + spaces. This is useful as a helper function to construct + `denote-file-name-slug-functions' ([Custom sluggification to + remove non-ASCII characters]). + + Function `denote-sluggify' + Make `STR' an appropriate slug for file name `COMPONENT' + ([Sluggification of file name components]). Apply the function + specified in `denote-file-name-slug-function' to `COMPONENT' + which is one of `title', `signature', `keyword'. If the + resulting string still contains consecutive `-',=_= or `=', they + are replaced by a single occurence of the character, if + necessary according to `COMPONENT'. If `COMPONENT' is `keyword', + remove underscores from `STR' as they are used as the keywords + separator in file names. + + Function `denote-sluggify-keyword' + Sluggify `STR' while joining separate words. + + Function `denote-sluggify-signature' + Make `STR' an appropriate slug for signatures ([Sluggification + of file name components]). + + Function `denote-sluggify-keywords' + Sluggify `KEYWORDS', which is a list of strings ([Sluggification + of file name components]). + + Function `denote-use-date' + The date to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-use-directory' + The directory to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-use-file-type' + The file type to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-use-keywords' + The keywords to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if `default'. Only ever `let' bind this, otherwise + the title will always be the same and the title prompt will be + skipped. + + Function `denote-use-signature' + The signature to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-use-template' + The template to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-use-title' + The title to be used in a note creation command. See the + documentation of `denote' for acceptable values. This variable + is ignored if nil. Only ever `let' bind this, otherwise the + title will always be the same and the title prompt will be + skipped. + + Function `denote-format-file-name' + Format file name. `DIR-PATH', `ID', `KEYWORDS', `TITLE', + `EXTENSION' and `SIGNATURE' are expected to be supplied by + `denote' or equivalent command. + + `DIR-PATH' is a string pointing to a directory. It ends with a + forward slash (the function `denote-directory' makes sure this + is the case when returning the value of the variable + `denote-directory'). `DIR-PATH' cannot be nil or an empty + string. + + `ID' is a string holding the identifier of the note. It can be + an empty string, in which case its respective file name + component is not added to the base file name. + + `DIR-PATH' and `ID' form the base file name. + + `KEYWORDS' is a list of strings that is reduced to a single + string by `denote-keywords-combine'. `KEYWORDS' can be an empty + list or a nil value, in which case the relevant file name + component is not added to the base file name. + + `TITLE' and `SIGNATURE' are strings. They can be an empty + string, in which case their respective file name component is + not added to the base file name. + + `EXTENSION' is a string that contains a dot followed by the file + type extension. It can be an empty string or a nil value, in + which case it is not added to the base file name. + + +[Custom sluggification to remove non-ASCII characters] See section 7.3.1 + +[Sluggification of file name components] See section 7.2 + + +20.3 Data retrieval interface for developers or advanced users +────────────────────────────────────────────────────────────── + + Function `denote-get-path-by-id' + Return absolute path of `ID' string in `denote-directory-files'. + + Function `denote-get-identifier-at-point' + Return the identifier at point or `POINT'. + + Function `denote-extract-keywords-from-path' + Extract keywords from `PATH' and return them as a list of + strings. `PATH' must be a Denote-style file name where keywords + are prefixed with an underscore. If `PATH' has no such + keywords, which is possible, return nil ([The file-naming + scheme]). + + Function `denote-extract-id-from-string' + Return existing Denote identifier in `STRING', else nil. + + Function `denote-retrieve-filename-identifier' + Extract identifier from `FILE' name, if present, else return + nil. To create a new one from a date, refer to the + `denote-get-identifier' function. + + Function `denote-retrieve-filename-title' + Extract Denote title component from `FILE' name, if present, + else return nil. + + Function `denote-retrieve-filename-keywords' + Extract keywords from `FILE' name, if present, else return + nil. Return matched keywords as a single string. + + Function `denote-retrieve-filename-signature' + Extract signature from `FILE' name, if present, else return nil. + + Function `denote-retrieve-title-or-filename' + Return appropriate title for `FILE' given its `TYPE'. This is a + wrapper for `denote-retrieve-front-matter-title-value' and + `denote-retrieve-filename-title'. + + Function `denote-get-identifier' + Convert `DATE' into a Denote identifier using + `denote-id-format'. If `DATE' is nil, return an empty string as + the identifier. + + Function `denote-retrieve-front-matter-title-value' + Return title value from `FILE' front matter per `FILE-TYPE'. + + Function `denote-retrieve-front-matter-title-line' + Return title line from `FILE' front matter per `FILE-TYPE'. + + Function `denote-retrieve-front-matter-keywords-value' + Return keywords value from `FILE' front matter per + `FILE-TYPE'. The return value is a list of strings. + + Function `denote-retrieve-front-matter-keywords-line' + Return keywords line from `FILE' front matter per `FILE-TYPE'. + + +[The file-naming scheme] See section 7 + + +20.4 Prompt interface for developers or advanced users +────────────────────────────────────────────────────── + + Function `denote-add-prompts' + Add list of `ADDITIONAL-PROMPTS' to `denote-prompts'. This is + best done inside of a `let' to create a wrapper function around + `denote', `denote-rename-file', and generally any command that + consults the value of `denote-prompts'. + + Function `denote-signature-prompt' + Prompt for signature string. With optional `INITIAL-SIGNATURE' + use it as the initial minibuffer text. With optional + `PROMPT-TEXT' use it in the minibuffer instead of the default + prompt. Previous inputs at this prompt are available for + minibuffer completion if the user option + `denote-history-completion-in-prompts' is set to a non-nil value + ([The `denote-history-completion-in-prompts' option]). + + Function `denote-file-prompt' + Prompt for file in variable `denote-directory'. Files that match + `denote-excluded-files-regexp' are excluded from the list. With + optional `FILES-MATCHING-REGEXP', filter the candidates per the + given regular expression. With optional `PROMPT-TEXT', use it + instead of the default call to select a file. With optional + `NO-REQUIRE-MATCH' accept the given input as-is. Return the + absolute path to the matching file. + + Function `denote-keywords-prompt' + Prompt for one or more keywords. Read entries as separate when + they are demarcated by the `crm-separator', which typically is a + comma. With optional `PROMPT-TEXT', use it to prompt the user + for keywords. Else use a generic prompt. With optional + `INITIAL-KEYWORDS' use them as the initial minibuffer text. + + Function `denote-title-prompt' + Prompt for title string. With optional `INITIAL-TITLE' use it as + the initial minibuffer text. With optional `PROMPT-TEXT' use it + in the minibuffer instead of the default prompt. Previous inputs + at this prompt are available for minibuffer completion if the + user option `denote-history-completion-in-prompts' is set to a + non-nil value ([The `denote-history-completion-in-prompts' + option]). + + Variable `denote-title-prompt-current-default' + Currently bound default title for `denote-title-prompt'. Set + the value of this variable within the lexical scope of a command + that needs to supply a default title before calling + `denote-title-prompt' and use `unwind-protect' to set its value + back to nil. + + Function `denote-file-type-prompt' + Prompt for `denote-file-type'. Note that a non-nil value other + than `text', `markdown-yaml', and `markdown-toml' falls back to + an Org file type. We use `org' here for clarity. + + Function `denote-date-prompt' + Prompt for date, expecting `YYYY-MM-DD' or that plus `HH:MM' (or + even `HH:MM:SS'). Use Org’s more advanced date selection utility + if the user option `denote-date-prompt-use-org-read-date' is + non-nil. It requires Org ([The + denote-date-prompt-use-org-read-date option]). With optional + `INITIAL-DATE' use it as the initial minibuffer text. With + optional `PROMPT-TEXT' use it in the minibuffer instead of the + default prompt. `INITIAL-DATE' is a string that can be processed + by `denote-valid-date-p', a value that can be parsed by + `decode-time' or nil. + + Function `denote-command-prompt' + Prompt for command among `denote-commands-for-new-notes' + ([Points of entry]). + + Variable `denote-prompts-with-history-as-completion' + Prompts that conditionally perform completion against their + history. These are minibuffer prompts that ordinarily accept a + free form string input, as opposed to matching against a + predefined set. These prompts can optionally perform completion + against their own minibuffer history when the user option + `denote-history-completion-in-prompts' is set to a non-nil value + ([The `denote-history-completion-in-prompts' option]). + + Function `denote-files-matching-regexp-prompt' + Prompt for `REGEXP' to filter Denote files by. With optional + `PROMPT-TEXT' use it instead of a generic prompt. + + Function `denote-prompt-for-date-return-id' + Use `denote-date-prompt' and return it as `denote-id-format'. + + Function `denote-template-prompt' + Prompt for template key in `denote-templates' and return its + value. + + Function `denote-subdirectory-prompt' + Prompt for subdirectory of the variable `denote-directory'. The + table uses the `file' completion category (so it works with + packages such as `marginalia' and `embark'). + + +[The `denote-history-completion-in-prompts' option] See section 5.1.2 + +[The denote-date-prompt-use-org-read-date option] See section 5.1.7 + +[Points of entry] See section 5 + + +20.5 Front matter interface for developers or advanced users +──────────────────────────────────────────────────────────── + + Function `denote-filetype-heuristics' + Return likely file type of `FILE'. If in the process of + `org-capture', consider the file type to be that of + Org. Otherwise, use the file extension to detect the file type + of `FILE'. + + If more than one file type correspond to this file extension, + use the first file type for which the :title-key-regexp in + `denote-file-types' matches in the file. + + Return nil if the file type is not recognized. + + Variable `denote-org-front-matter' + Specifies the Org front matter. It is passed to `format' with + arguments `TITLE', `DATE', `KEYWORDS', `ID' ([Change the front + matter format]) + + Variable `denote-yaml-front-matter' + Specifies the YAML (Markdown) front matter. It is passed to + `format' with arguments `TITLE', `DATE', `KEYWORDS', `ID' + ([Change the front matter format]) + + Variable `denote-toml-front-matter' + Specifies the TOML (Markdown) front matter. It is passed to + `format' with arguments `TITLE', `DATE', `KEYWORDS', `ID' + ([Change the front matter format]) + + Variable `denote-text-front-matter' + Specifies the plain text front matter. It is passed to `format' + with arguments `TITLE', `DATE', `KEYWORDS', `ID' ([Change the + front matter format]) + + Function `denote-date-org-timestamp' + Format `DATE' using the Org inactive timestamp notation. + + Function `denote-date-rfc3339' + Format `DATE' using the RFC3339 specification. + + Function `denote-date-iso-8601' + Format `DATE' according to ISO 8601 standard. + + Function `denote-trim-whitespace' + Trim whitespace around string `S'. This can be used in + `denote-file-types' to format front mattter. + + Function `denote-trim-whitespace-then-quotes' + Trim whitespace then quotes around string `S'. This can be used + in `denote-file-types' to format front mattter. + + Function `denote-format-string-for-org-front-matter' + Return string `S' as-is for Org or plain text front matter. If + `S' is not a string, return an empty string. + + Function `denote-format-string-for-md-front-matter' + Surround string `S' with quotes. If `S' is not a string, return + a literal emptry string. This can be used in `denote-file-types' + to format front mattter. + + Function `denote-format-keywords-for-md-front-matter' + Format front matter `KEYWORDS' for markdown file type. + `KEYWORDS' is a list of strings. Consult the + `denote-file-types' for how this is used. + + Function `denote-format-keywords-for-text-front-matter' + Format front matter `KEYWORDS' for text file type. `KEYWORDS' + is a list of strings. Consult the `denote-file-types' for how + this is used. + + Function `denote-format-keywords-for-org-front-matter' + Format front matter `KEYWORDS' for org file type. `KEYWORDS' is + a list of strings. Consult the `denote-file-types' for how this + is used. + + Function `denote-extract-keywords-from-front-matter' + Format front matter `KEYWORDS' for org file type. `KEYWORDS' is + a list of strings. Consult the `denote-file-types' for how this + is used. + + Variable `denote-file-types' + Alist of `denote-file-type' and their format properties. + + Each element is of the form `(SYMBOL PROPERTY-LIST)'. `SYMBOL' + is one of those specified in `denote-file-type' or an arbitrary + symbol that defines a new file type. + + `PROPERTY-LIST' is a plist that consists of the following + elements: + + 1. `:extension' is a string with the file extension including + the period. + + 2. `:date-function' is a function that can format a date. See + the functions `denote--date-iso-8601', + `denote--date-rfc3339', and `denote--date-org-timestamp'. + + 3. `:front-matter' is either a string passed to `format' or a + variable holding such a string. The `format' function + accepts four arguments, which come from `denote' in this + order: `TITLE', `DATE', `KEYWORDS', `IDENTIFIER'. Read the + doc string of `format' on how to reorder arguments. + + 4. `:title-key-regexp' is a regular expression that is used to + retrieve the title line in a file. The first line matching + this regexp is considered the title line. + + 5. `:title-value-function' is the function used to format the + raw title string for inclusion in the front matter (e.g. to + surround it with quotes). Use the `identity' function if no + further processing is required. + + 6. `:title-value-reverse-function' is the function used to + retrieve the raw title string from the front matter. It + performs the reverse of `:title-value-function'. + + 7. `:keywords-key-regexp' is a regular expression used to + retrieve the keywords’ line in the file. The first line + matching this regexp is considered the keywords’ line. + + 8. `:keywords-value-function' is the function used to format the + keywords’ list of strings as a single string, with + appropriate delimiters, for inclusion in the front matter. + + 9. `:keywords-value-reverse-function' is the function used to + retrieve the keywords’ value from the front matter. It + performs the reverse of the `:keywords-value-function'. + + 10. `:link' is a string, or variable holding a string, that + specifies the format of a link. See the variables + `denote-org-link-format', `denote-md-link-format'. + + 11. `:link-in-context-regexp' is a regular expression that is + used to match the aforementioned link format. See the + variables `denote-org-link-in-context-regexp', + `denote-md-link-in-context-regexp'. + + If `denote-file-type' is nil, use the first element of this list + for new note creation. The default is `org'. + + +[Change the front matter format] See section 8.1 + + +20.6 Link interface for developers or advanced users +──────────────────────────────────────────────────── + + Variable `denote-org-link-format' + Format of Org link to note. The value is passed to `format' + with `IDENTIFIER' and `TITLE' arguments, in this order. Also + see `denote-org-link-in-context-regexp'. + + Variable `denote-md-link-format' + Format of Markdown link to note. The `%N$s' notation used in + the default value is for `format' as the supplied arguments are + `IDENTIFIER' and `TITLE', in this order. Also see + `denote-md-link-in-context-regexp'. + + Variable `denote-id-only-link-format' + Format of identifier-only link to note. The value is passed to + `format' with `IDENTIFIER' as its sole argument. Also see + `denote-id-only-link-in-context-regexp'. + + Variable `denote-org-link-in-context-regexp' + Regexp to match an Org link in its context. The format of such + links is `denote-org-link-format'. + + Variable `denote-md-link-in-context-regexp' + Regexp to match an Markdown link in its context. The format of + such links is `denote-md-link-format'. + + Variable `denote-id-only-link-in-context-regexp' + Regexp to match an identifier-only link in its context. The + format of such links is `denote-id-only-link-format'. + + Function `denote-select-linked-file-prompt' + Prompt for linked file among `FILES'. + + Function `denote-link-return-links' + Return list of links in current or optional `FILE'. Also see + `denote-link-return-backlinks'. + + Function `denote-link-return-backlinks' + Return list of backlinks in current or optional `FILE'. Also + see `denote-link-return-links'. + + Function `denote-link-description-with-signature-and-title' + Return link description for `FILE'. Produce a description as + follows: + + • If the region is active, use it as the description. + + • If `FILE' as a signature, then use the + `denote-link-signature-format'. By default, this looks like + “signature title”. + + • If `FILE' does not have a signature, then use its title as the + description. + + Variable `denote-link-description-function' + Function to use to create the description of links. The function + specified should take a `FILE' argument and should return the + description as a string. By default, the title of the file is + returned as the description. + + +20.7 Xref interface for developers or advanced users +──────────────────────────────────────────────────── + + Function `denote-retrieve-groups-xref-query' + Access location of xrefs for `QUERY' and group them per + file. Limit the search to text files. + + Function `denote-retrieve-files-xref-query' + Return sorted, deduplicated file names with matches for `QUERY' + in their contents. Limit the search to text files. + + Function `denote-retrieve-xref-alist' + Return xref alist of files with location of matches for + `QUERY'. With optional `FILES-MATCHING-REGEXP', limit the list + of files accordingly (per `denote-directory-files'). At all + times limit the search to text files. + + +20.8 Renaming files interface for developers or advanced users +────────────────────────────────────────────────────────────── + + Function `denote-rename-file-prompt' + Prompt to rename file named `OLD-NAME' to `NEW-NAME'. If + `denote-rename-confirmations' does not contain + `modify-file-name', return t without prompting. + + Function `denote-rename-file-and-buffer' + Rename file named `OLD-NAME' to `NEW-NAME', updating buffer + name. + + Function `denote-prepend-front-matter' + Prepend front matter to `FILE'. The `TITLE', `KEYWORDS', `DATE', + `ID', `SIGNATURE', and `FILE-TYPE' are passed from the renaming + command and are used to construct a new front matter block if + appropriate. + + Function `denote-rewrite-front-matter' + Rewrite front matter of note after `denote-rename-file' (or + related). The `FILE', `TITLE', `KEYWORDS', `SIGNATURE', `DATE', + `IDENTIFIER', and `FILE-TYPE' arguments are given by the + renaming command and are used to construct new front matter + values if appropriate. If `denote-rename-confirmations' contains + `rewrite-front-matter', prompt to confirm the rewriting of the + front matter. Otherwise produce a `y-or-n-p' prompt to that + effect. + + Function `denote-add-front-matter-prompt' + Prompt to add a front-matter to `FILE'. Return non-nil if a new + front matter should be added. If `denote-rename-confirmations' + does not contain `add-front-matter', return t without prompting. + + Function `denote-rewrite-keywords' + Rewrite `KEYWORDS' in `FILE' outright according to + `FILE-TYPE'. Do the same as `denote-rewrite-front-matter' for + keywords, but do not ask for confirmation. With optional + `SAVE-BUFFER', save the buffer corresponding to `FILE'. This + function is for use in the commands `denote-keywords-add', + `denote-keywords-remove', `denote-dired-rename-files', or + related. + + Function `denote-update-dired-buffers' + Update Dired buffers of variable `denote-directory'. Also revert + the current Dired buffer even if it is not inside the + `denote-directory'. Note that the `denote-directory' accepts a + directory-local value for what we internally refer to as “silos” + ([Maintain separate directories for notes]). + + +[Maintain separate directories for notes] See section 5.7 + + +21 Troubleshoot Denote in a pristine environment +════════════════════════════════════════════════ + + Sometimes we get reports on bugs that may not be actually caused by + some error in the Denote code base. To help gain insight into what + the problem is, we need to be able to reproduce the issue in a minimum + viable system. Below is one way to achieve this. + + 1. Find where your `denote.el' file is stored on your filesystem. + + 2. Assuming you have already installed the package, one way to do this + is to invoke `M-x find-library' and search for `denote'. It will + take you to the source file. There do `M-x eval-expression', which + will bring up a minibuffer prompt. At the prompt evaluate: + + ┌──── + │ (kill-new (expand-file-name (buffer-file-name))) + └──── + + 1. The above will save the full file system path to your kill ring. + + 2. In a terminal emulator or an `M-x shell' buffer execute: + + ┌──── + │ emacs -Q + └──── + + 1. This will open a new instance of Emacs in a pristine environment. + Only the default settings are loaded. + + 2. In the `*scratch*' buffer of `emacs -Q', add your configurations + like the following and try to reproduce the issue: + + ┌──── + │ (require 'denote "/full/path/to/what/you/got/denote.el") + │ + │ ;; Your configurations here + └──── + + Then try to see if your problem still occurs. If it does, then the + fault is with Denote. Otherwise there is something external to it + that we need to account for. Whatever the case, this exercise helps + us get a better sense of the specifics. + + +22 Contributing +═══════════════ + + Denote is a GNU ELPA package. As such, any significant change to the + code requires copyright assignment to the Free Software Foundation + (more below). + + You do not need to be a programmer to contribute to this package. + Sharing an idea or describing a workflow is equally helpful, as it + teaches us something we may not know and might be able to cover either + by extending Denote or expanding this manual. If you prefer to write a + blog post, make sure you share it with us: we can add a section herein + referencing all such articles. Everyone gets acknowledged + ([Acknowledgements]). There is no such thing as an “insignificant + contribution”—they all matter. + + ⁃ Package name (GNU ELPA): `denote' + ⁃ Official manual: + ⁃ Change log: + ⁃ Git repositories: + ⁃ GitHub: + ⁃ GitLab: + + If our public media are not suitable, you are welcome to contact me + (Protesilaos) in private: . + + Copyright assignment is a prerequisite to sharing code. It is a simple + process. Check the request form below (please adapt it accordingly). + You must write an email to the address mentioned in the form and then + wait for the FSF to send you a legal agreement. Sign the document and + file it back to them. This could all happen via email and take about a + week. You are encouraged to go through this process. You only need to + do it once. It will allow you to make contributions to Emacs in + general. + + ┌──── + │ Please email the following information to assign@gnu.org, and we + │ will send you the assignment form for your past and future changes. + │ + │ Please use your full legal name (in ASCII characters) as the subject + │ line of the message. + │ + │ REQUEST: SEND FORM FOR PAST AND FUTURE CHANGES + │ + │ [What is the name of the program or package you're contributing to?] + │ + │ GNU Emacs + │ + │ [Did you copy any files or text written by someone else in these changes? + │ Even if that material is free software, we need to know about it.] + │ + │ Copied a few snippets from the same files I edited. Their author, + │ Protesilaos Stavrou, has already assigned copyright to the Free Software + │ Foundation. + │ + │ [Do you have an employer who might have a basis to claim to own + │ your changes? Do you attend a school which might make such a claim?] + │ + │ + │ [For the copyright registration, what country are you a citizen of?] + │ + │ + │ [What year were you born?] + │ + │ + │ [Please write your email address here.] + │ + │ + │ [Please write your postal address here.] + │ + │ + │ + │ + │ + │ [Which files have you changed so far, and which new files have you written + │ so far?] + │ + └──── + + +[Acknowledgements] See section 26 + +22.1 Wishlist of what we can do to extend Denote +──────────────────────────────────────────────── + + These are various ideas to extend Denote. Whether they should be in + the core package or a separate extension is something we can discuss. + I, Protesilaos, am happy to help anyone who wants to do any of this. + + denote-embark.el + Provide integration with the `embark' package. This can be for + doing something with the identifier/link at point. For example, + it could provide an action to produce backlinks for the + identifier/file we are linking to, not just the current one. + + denote-transient.el + The `transient' package is built into Emacs 29 (Denote supports + Emacs 28 though). We can use it to define an alternative to what + we have for the menu bar. Perhaps this interface can used to + toggle various options, such as to call `denote' with a + different set of prompts. + + A `denote-directories' user option + This can be either an extension of the `denote-directory' + (accept a list of file paths value) or a new variable. The idea + is to let the user define separate Denote directories which do + know about the presence of each other (unlike silos). This way, + a user can have an entry in `~/Documents/notes/' link to + something `~/Git/projects/' and everything work as if the + `denote-directory' is set to the `~/' (with the status quo as of + 2024-02-18 08:27 +0200). + + Encode the day in the identifier + The idea is to use some coded reference for Monday, Tuesday, + etc. instead of having the generic `T' in the identifier. For + example, Monday is `A' so the identifier for it is something + like `20240219A101522' instead of what we now have as + `20240219T101522'. The old method should still be supported. + Apart from changing a few regular expressions, this does not + seem too complex to me. We would need a user option to opt in to + such a feature. Then tweak the relevant parts. The tricky issue + is to define a mapping of day names to letters/symbols that + works for everyone. Do all countries have a seven-day week, for + example? We need something universally applicable here. + + Anything else? You are welcome to discuss these and/or add to the + list. + + +23 Publications about Denote +════════════════════════════ + + The Emacs community is putting Denote to great use. This section + includes publications that show how people configure their note-taking + setup. If you have a blog post, video, or configuration file about + Denote, feel welcome to tell us about it ([Contributing]). + + ⁃ David Wilson (SystemCrafters): /Generating a Blog Site from Denote + Entries/, 2022-09-09, + + ⁃ David Wilson (SystemCrafters): /Trying Out Prot’s Denote, an Org + Roam Alternative?/, 2022-07-15, + + + ⁃ Jack Baty: /Keeping my Org Agenda updated based on Denote keywords/, + 2022-11-30, + + ⁃ Jeremy Friesen: /Denote Emacs Configuration/, 2022-10-02, + + + ⁃ Jeremy Friesen: /Exploring the Denote Emacs Package/, 2022-10-01, + + + ⁃ Jeremy Friesen: /Migration Plan for Org-Roam Notes to Denote/, + 2022-10-02, + + + ⁃ Jeremy Friesen: /Project Dispatch Menu with Org Mode Metadata, + Denote, and Transient/, 2022-11-19, + + + ⁃ Mohamed Suliman: /Managing a bibliography of BiBTeX entries with + Denote/, 2022-12-20, + + + ⁃ Peter Prevos: /Simulating Text Files with R to Test the Emacs Denote + Package/, 2022-07-28, + + + ⁃ Peter Prevos: /Emacs Writing Studio/, 2023-10-19. A configuration + for authors, using Denote for taking notes, literature reviews and + manage collections of images: + • + • + • + • + + ⁃ Stefan Thesing: /Denote as a Zettelkasten/, 2023-03-02, + + + ⁃ Summer Emacs: /An explanation of how I use Emacs/, 2023-05-04, + + + +[Contributing] See section 22 + + +24 Alternatives to Denote +═════════════════════════ + + What follows is a list of Emacs packages for note-taking. I + (Protesilaos) have not used any of them, as I was manually applying my + file-naming scheme beforehand and by the time those packages were + available I was already hacking on the predecessor of Denote as a + means of learning Emacs Lisp (a package which I called “Unassuming + Sidenotes of Little Significance”, aka “USLS” which is pronounced as + “U-S-L-S” or “useless”). As such, I cannot comment at length on the + differences between Denote and each of those packages, beside what I + gather from their documentation. + + [org-roam] + The de facto standard in the Emacs milieu—and rightly so! It + has a massive community, is featureful, and should be an + excellent companion to anyone who is invested in the Org + ecosystem and/or knows what “Roam” is (I don’t). It has been + explained to me that Org Roam uses a database to store a cache + about your notes. It otherwise uses standard Org files. The + cache helps refer to the same node through aliases which can + provide lots of options. Personally, I follow a + single-topic-per-note approach, so anything beyond that is + overkill. If the database is only for a cache, then maybe that + has no downside, though I am careful with any kind of + specialised program as it creates a dependency. If you ask me + about database software in particular, I have no idea how to use + one, let alone debug it or retrieve data from it if something + goes awry (I could learn, but that is beside the point). + + [zk (or zk.el)] + Reading its documentation makes me think that this is Denote’s + sibling—the two projects have a lot of things in common, + including the preference to rely on plain files and standard + tools. The core difference is that Denote has a strict + file-naming scheme. Other differences in available features + are, in principle, matters of style or circumstance: both + packages can have them. As its initials imply, ZK enables a + zettelkasten-like workflow. It does not enforce it though, + letting the user adapt the method to their needs and + requirements. + + [zettelkasten] + This is another one of Denote’s relatives, at least insofar as + the goal of simplicity is concerned. The major difference is + that according to its documentation “the name of the file that + is created is just a unique ID”. This is not consistent with + our file-naming scheme which is all about making sense of your + files by their name alone and being able to visually parse a + listing of them without any kind of specialised tool (e.g. `ls + -l' or `ls -C' on the command-line from inside the + `denote-directory' give you a human-readable set of files names, + while `find * -maxdepth 0 -type f' is another approach). + + [zetteldeft] + This is a zettelkasten note-taking system built on top of the + `deft' package. Deft provides a search interface to a + directory, in this case the one holding the user’s `zetteldeft' + notes. Denote has no such dependency and is not opinionated + about how the user prefers to search/access their notes: use + Dired, Grep, the `consult' package, or whatever else you already + have set up for all things Emacs, not just your notes. + + Searching through `M-x list-packages' for “zettel” brings up more + matches. `zetteldesk' is an extension to Org Roam and, as such, I + cannot possibly know what Org Roam truly misses and what the + added-value of this package is. `neuron-mode' builds on top of an + external program called `neuron', which I have never used. + + Searching for “note” gives us a few more results. `notes-mode' has + precious little documentation and I cannot tell what it actually does + (as I said in my presentation for LibrePlanet 2022, inadequate docs + are a bug). `side-notes' differs from what we try to do with Denote, + as it basically gives you the means to record your thoughts about some + other project you are working on and keep them on the side: so it and + Denote should not be mutually exclusive. + + If I missed something, please let me know. + + +[org-roam] + +[zk (or zk.el)] + +[zettelkasten] + +[zetteldeft] + +24.1 Alternative implementations and further reading +──────────────────────────────────────────────────── + + This section covers blog posts and implementations from the Emacs + community about the topic of note-taking and file organization. They + may refer to some of the packages covered in the previous section or + provide their custom code ([Alternatives to Denote]). The list is + unsorted. + + ⁃ José Antonio Ortega Ruiz (aka “jao”) explains a note-taking method + that is simple like Denote but differs in other ways. An + interesting approach overall: + . + + ⁃ Jethro Kuan (the main `org-roam' developer) explains their + note-taking techniques: + . Good ideas all + round, regardless of the package/code you choose to use. + + ⁃ Karl Voit’s tools [date2name], [filetags], [appendfilename], and + [move2archive] provide a Python-based implementation to organize + individual files which do not require Emacs. His approach ([blog + post] and his [presentation at GLT18]) has been complemented by + [memacs] to process e.g., the date of creation of photographs, or + the log of a phone call in a format compatible to org. + + [ Development note: help expand this list. ] + + +[Alternatives to Denote] See section 24 + +[date2name] + +[filetags] + +[appendfilename] + +[move2archive] + +[blog post] + +[presentation at GLT18] + +[memacs] + + +25 Frequently Asked Questions +═════════════════════════════ + + I (Protesilaos) answer some questions I have received or might get. + It is assumed that you have read the rest of this manual: I will not + go into the specifics of how Denote works. + + +25.1 Why develop Denote when PACKAGE already exists? +──────────────────────────────────────────────────── + + I wrote Denote because I was using a variant of Denote’s file-naming + scheme before I was even an Emacs user (I switched to Emacs from + Tmux+Vim+CLI in the summer of 2019). I was originally inspired by + Jekyll, the static site generator, which I started using for my + website in 2016 (was on WordPress before). Jekyll’s files follow the + `YYYY-MM-DD-TITLE.md' pattern. I liked its efficiency relative to the + unstructured mess I had before. Eventually, I started using that + scheme outside the confines of my website’s source code. Over time I + refined it and here we are. + + Note-taking is something I take very seriously, as I am a prolific + writer (just check my website, which only reveals the tip of the + iceberg). As such, I need a program that does exactly what I want and + which I know how to extend. I originally tried to use Org capture + templates to create new files with a Denote-style file-naming scheme + but never managed to achieve it. Maybe because `org-capture' has some + hard-coded assumptions or I simply am not competent enough to hack on + core Org facilities. Whatever the case, an alternative was in order. + + The existence of PACKAGE is never a good reason for me not to conduct + my own experiments for recreational, educational, or practical + purposes. When the question arises of “why not contribute to PACKAGE + instead?” the answer is that without me experimenting in the first + place, I would lack the skills for such a task. Furthermore, + contributing to another package does not guarantee I get what I want + in terms of workflow. + + Whether you should use Denote or not is another matter altogether: + choose whatever you want. + + +25.2 Why not rely exclusively on Org? +───────────────────────────────────── + + I think Org is one of Emacs’ killer apps. I also believe it is not + the right tool for every job. When I write notes, I want to focus on + writing. Nothing more. I thus have no need for stuff like org-babel, + scheduling to-do items, clocking time, and so on. The more “mental + dependencies” you add to your workflow, the heavier the burden you + carry and the less focused you are on the task at hand: there is + always that temptation to tweak the markup, tinker with some syntactic + construct, obsess about what ought to be irrelevant to writing as + such. + + In technical terms, I also am not fond of Org’s code base (I + understand why it is the way it is—just commenting on the fact). Ever + tried to read it? You will routinely find functions that are + tens-to-hundreds of lines long and have all sorts of special casing. + As I am not a programmer and only learnt to write Elisp through trial + and error, I have no confidence in my ability to make Org do what I + want at that level, hence `denote' instead of `org-denote' or + something. + + Perhaps the master programmer is one who can deal with complexity and + keep adding to it. I am of the opposite view, as language—code + included—is at its communicative best when it is clear and accessible. + + Make no mistake: I use Org for the agenda and also to write technical + documentation that needs to be exported to various formats, including + this very manual. + + +25.3 Why care about Unix tools when you use Emacs? +────────────────────────────────────────────────── + + My notes form part of my longer-term storage. I do not want to have + to rely on a special program to be able to read them or filter them. + Unix is universal, at least as far as I am concerned. + + Denote streamlines some tasks and makes things easier in general, + which is consistent with how Emacs provides a layer of interactivity + on top of Unix. Still, Denote’s utilities can, in principle, be + implemented as POSIX shell scripts (minus the Emacs-specific parts + like fontification in Dired or the buttonization of links). + + Portability matters. For example, in the future I might own a + smartphone, so I prefer not to require Emacs, Org, or some other + executable to access my files on the go. + + Furthermore, I might want to share those files with someone. If I + make Emacs a requirement, I am limiting my circle to a handful of + relatively advanced users. + + Please don’t misinterpret this: I am using Emacs full-time for my + computing and maintain a growing list of packages for it. This is + just me thinking long-term. + + +25.4 Why many small files instead of few large ones? +──────────────────────────────────────────────────── + + I have read that Org favours the latter method. If true, I strongly + disagree with it because of the implicit dependency it introduces and + the way it favours machine-friendliness over human-readability in + terms of accessing information. Notes are long-term storage. I might + want to access them on (i) some device with limited features, (ii) + print on paper, (iii) share with another person who is not a tech + wizard. + + There are good arguments for few large files, but all either + prioritize machine-friendliness or presuppose the use of sophisticated + tools like Emacs+Org. + + Good luck using `less' on a generic TTY to read a file with a zillion + words, headings, sub-headings, sub-sub-headings, property drawers, and + other constructs! You will not get the otherwise wonderful folding of + headings the way you do in Emacs—do not take such features for + granted. + + My point is that notes should be atomic to help the user—and + potentially the user’s family, friends, acquaintances—make sense of + them in a wide range of scenaria. The more program-agnostic your file + is, the better for you and/or everyone else you might share your + writings with. + + Human-readability means that we optimize for what matters to us. If + (a) you are the only one who will ever read your notes, (b) always + have access to good software like Emacs+Org, (c) do not care about + printing on paper, then Denote’s model is not for you. Maybe you need + to tweak some `org-capture' template to append a new entry to one mega + file (I do that for my Org agenda, by the way, as I explained before + about using the right tool for the job). + + +25.5 Does Denote perform well at scale? +─────────────────────────────────────── + + Denote does not do anything fancy and has no special requirements: it + uses standard tools to accomplish ordinary tasks. If Emacs can cope + with lots of files, then that is all you need to know: Denote will + work. + + To put this to the test, Peter Prevos is running simulations with R + that generate large volumes of notes. You can read the technicalities + here: . + Excerpt: + + Using this code I generated ten thousands notes and used + this to test the Denote package to see it if works at a + large scale. This tests shows that Prot’s approach is + perfectly capable of working with thousands of notes. + + Of course, we are always prepared to make refinements to the code, + where necessary, without compromising on the project’s principles. + + +25.6 I add TODOs to my notes; will many files slow down the Org agenda? +─────────────────────────────────────────────────────────────────────── + + Yes, many files will slow down the agenda due to how that works. Org + collects all files specified in the `org-agenda-files', searches + through their contents for timestamped entries, and then loops through + all days to determine where each entry belongs. The more days and + more files, the longer it takes to build the agenda. Doing this with + potentially hundreds of files will have a noticeable impact on + performance. + + This is not a deficiency of Denote. It happens with generic Org + files. The way the agenda is built is heavily favoring the use of a + single file that holds all your timestamped entries (or at least a few + such files). Tens or hundreds of files are inefficient for this job. + Plus doing so has the side-effect of making Emacs open all those + files, which you probably do not need. + + If you want my opinion though, be more forceful with the separation of + concerns. Decouple your knowledge base from your ephemeral to-do + list: Denote (and others) can be used for the former, while you let + standard Org work splendidly for the latter—that is what I do, anyway. + + Org has a powerful linking facility, whether you use `org-store-link' + or do it via an `org-capture' template. If you want a certain note to + be associated with a task, just store the task in a single `tasks.org' + (or however you name it) and link to the relevant context. + + Do not mix your knowledge base with your to-do items. If you need + help figuring out the specifics of this workflow, you are welcome to + ask for help in our relevant channels ([Contributing]). + + +[Contributing] See section 22 + + +25.7 I want to sort by last modified in Dired, why won’t Denote let me? +─────────────────────────────────────────────────────────────────────── + + Denote does not control how Dired sorts files. I encourage you to read + the manpage of the `ls' executable. It will help you in general, while + it applies to Emacs as well via Dired. The gist is that you can update + the `ls' flags that Dired uses on-the-fly: type `C-u M-x + dired-sort-toggle-or-edit' (`C-u s' by default) and append + `--sort=time' at the prompt. To reverse the order, add the `-r' flag. + The user option `dired-listing-switches' sets your default preference. + + For an on-demand sorted and filtered Dired listing of Denote files, + use the command `denote-sort-dired' ([Sort files by component]). + + +[Sort files by component] See section 14 + + +25.8 How do you handle the last modified case? +────────────────────────────────────────────── + + Denote does not insert any meta data or heading pertaining to edits in + the file. I am of the view that these either do not scale well or are + not descriptive enough. Suppose you use a “lastmod” heading with a + timestamp: which lines where edited and what did the change amount to? + + This is where an external program can be helpful. Use a Version + Control System, such as Git, to keep track of all your notes. Every + time you add a new file, record the addition. Same for post-creation + edits. Your VCS will let you review the history of those changes. + For instance, Emacs’ built-in version control framework has a command + that produces a log of changes for the current file: `M-x + vc-print-log', bound to `C-x v l' by default. From there one can + access the corresponding diff output (use `M-x describe-mode' (`C-h + m') in an unfamiliar buffer to learn more about it). With Git in + particular, Emacs users have the option of the all-round excellent + `magit' package. + + In short: let Denote (or equivalent) create notes and link between + them, the file manager organise and provide access to files, search + programs deal with searching and narrowing, and version control + software handle the tracking of changes. + + +25.9 Why are some Org links opening outside Emacs? +────────────────────────────────────────────────── + + Org has its own mechanism to determine how best to open a link. This + affects the `file:' link type, but also the `denote:' one (which is + designed to be as close to `file:' as possible). + + When following a link, Org usually displays the data in an Emacs + buffer, though it might launch an external application instead. The + idea is to use a specialised program when that is relevant, such as to + display a video. Though there can be scenaria the user does not like, + such as when Org decides to load `.md' or `.html' files with an + external app. To compound the problem, users can name any file type + using the Denote file-naming scheme, including images, PDFs, videos, + and more ([Renaming files]). + + To instruct Org to stay in Emacs for such cases, the user needs to + modify the variable `org-file-apps', which is not specific to Denote. + As one use-case, `org-file-apps' associates a regular expression to + match file names with a method on how to display them (do `M-x + describe-variable' and then search for `org-file-apps' to read its + documentation). Thus, the user can use something like the following in + their Org or Denote configuration: + + ┌──── + │ ;; Tell Org to use Emacs when opening files that end in .md + │ (add-to-list 'org-file-apps '("\\.md\\'" . emacs)) + │ + │ ;; Do the same for .html + │ (add-to-list 'org-file-apps '("\\.html\\'" . emacs)) + └──── + + Each of these adds a new entry to the existing value of that user + option. Replace `md' or `html' with the desired file type extension. + + +[Renaming files] See section 6 + + +25.10 Speed up backlinks’ or query links’ buffer creation? +────────────────────────────────────────────────────────── + + Denote leverages the built-in `xref' library to search for the + identifier of the current file and return any links to it. For users + of Emacs version 28 or higher, there exists a user option to specify + the program that performs this search: `xref-search-program'. The + default is `grep', which can be slow, though one may opt for `ugrep', + `ripgrep', or even specify something else (read the doc string of that + user option for the details). + + Try either for these for better results: + + ┌──── + │ (setq xref-search-program 'ripgrep) + │ + │ ;; OR + │ + │ (setq xref-search-program 'ugrep) + └──── + + To use whatever executable is available on your system, use something + like this: + + ┌──── + │ ;; Prefer ripgrep, then ugrep, and fall back to regular grep. + │ (setq xref-search-program + │ (cond + │ ((or (executable-find "ripgrep") + │ (executable-find "rg")) + │ 'ripgrep) + │ ((executable-find "ugrep") + │ 'ugrep) + │ (t + │ 'grep))) + └──── + + +25.11 Why do I get “Search failed with status 1” when I search for backlinks? +───────────────────────────────────────────────────────────────────────────── + + Denote uses [Emacs’ Xref] to find backlinks. Xref requires `xargs' + and one of `grep' or `ripgrep', depending on your configuration. + + This is usually not an issue on *nix systems, but the necessary + executables are not available on Windows Emacs distributions. Please + ensure that you have both `xargs' and either `grep' or `ripgrep' + available within your `PATH' environment variable. + + If you have `git' on Windows installed, then you may use the following + code (adjust the git’s installation path if necessary): + ┌──── + │ (setenv "PATH" (concat (getenv "PATH") ";" "C:\\Program Files\\Git\\usr\\bin")) + └──── + + +[Emacs’ Xref] + + +25.12 Why do I get a double `#+title' in Doom Emacs? +──────────────────────────────────────────────────── + + Doom Emacs provides a set of bespoke templates for Org. One of those + prefills any new Org file with a `#+title' field. So when Denote + creates a new Org file and inserts front matter to it, it inevitably + adds an extra title to the existing one. + + This is not a Denote problem. We can only expect a new file to be + empty by default. Check how to disable the relevant module in your + Doom Emacs configuration file. + + +26 Acknowledgements +═══════════════════ + + Denote is meant to be a collective effort. Every bit of help matters. + + Author/maintainer + Protesilaos Stavrou. + + Contributions to code or the manual + Abdul-Lateef Haji-Ali, Abin Simon, Adam Růžička, Alan Schmitt, + Alexandre Rousseau, Ashton Wiersdorf, Aziz, Benjamin Kästner, + Bruno Boal, Charanjit Singh, Claudio Migliorelli, Clemens + Radermacher, Colin McLear, Damien Cassou, Eduardo Grajeda, Elias + Storms, Eshel Yaron, Florian, Glenna D., Graham Marlow, Hilde + Rhyne, Ivan Sokolov, Jack Baty, Jakub Szczerbowski, Jean-Charles + Bagneris, Jean-Philippe Gagné Guay, Jianwei Hou, Joseph Turner, + Jürgen Hötzel, Kaushal Modi, Kai von Fintel, Kierin Bell, Kostas + Andreadis, Kristoffer Balintona, Kyle Meyer, Laurent Gatto, + Lucas Quintana, Maikol Solis, Marc Fargas, Matthew Lemon, Noboru + Ota (nobiot), Norwid Behrnd, Octavian, Peter Prevos, Philip + Kaludercic, Quiliro Ordóñez, Stephen R. Kifer, Stefan Monnier, + Stefan Thesing, Thibaut Benjamin, Tomasz Hołubowicz, TomoeMami , + Vedang Manerikar, Wesley Harvey, Zhenxu Xu, arsaber101, + bryanrinders, eum3l, ezchi, jarofromel, leinfink (Henrik), + l-o-l-h (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, + pmenair, relict007, skissue. + + Ideas and/or user feedback + Abin Simon, Aditya Yadav, Alan Schmitt, Aleksandr Vityazev, Alex + Griffin, Alex Hirschfeld, Alexis Purslane, Alfredo Borrás, Alp + Eren Kose, André Bering, Ashton Wiersdorf, Benjamin Kästner, + Claudio Migliorelli, Claudiu Tănăselia, Colin McLear, + Cosmin-Octavian C, Damien Cassou, Elias Storms, Federico + Stilman, Florian, Frédéric Willem Frank Ehmsen, Glenna D., Guo + Yong, Hanspeter Gisler Harold Ollivier, IceAsteroid, Jack Baty, + Jay Rajput, Jean-Charles Bagneris, Jeff Valk, Jens Östlund, + Jeremy Friesen, Jonathan Sahar, Johan Bolmsjö, Jonas + Großekathöfer, Jousimies, Juanjo Presa, Julian Hoch, Kai von + Fintel, Kaushal Modi, Kolmas, Lukas C. Bossert, M. Hadi Timachi, + Maikol Solis, Mark Olson, Mirko Hernandez, Niall Dooley, Nick + Bell, Oliver Epper, Paul van Gelder, Peter Prevos, Peter Smith, + Riccardo Giannitrapani, Samuel W. Flint, Sergio Rey, Suhail + Singh, Shreyas Ragavan, Stefan Thesing, Summer Emacs, Sven + Seebeck, Taoufik, TJ Stankus, Vick (VicZz), Viktor Haag, Vineet + C. Kulkarni, Wade Mealing, Wilf, Yi Liu, Ypot, atanasj, azegas, + babusri, bdillahu, coherentstate, doolio, duli, drcxd, elge70, + elliottw, fingerknight, hpgisler, hyperfocus1337, jtpavlock, + juh, leafarbelm, mentalisttraceur, pRot0ta1p, rbenit68, + relict007, sarcom-sar, sienic, skissue, sundar bp, + yetanotherfossman, zadca123 + + Special thanks to Peter Povinec who helped refine the file-naming + scheme, which is the cornerstone of this project. + + Special thanks to Jean-Philippe Gagné Guay for the numerous + contributions to the code base. + + +27 GNU Free Documentation License +═════════════════════════════════ + + +28 Indices +══════════ + +28.1 Function index +─────────────────── + + +28.2 Variable index +─────────────────── + + +28.3 Concept index +────────────────── blob - /dev/null blob + 08540f63c17e06cdba24c8ce6c447e6950d89c6e (mode 644) --- /dev/null +++ elpa/denote-4.0.0/README.md @@ -0,0 +1,24 @@ +# denote: Simple notes with an efficient file-naming scheme + +Denote is a simple note-taking tool for Emacs. It is based on the idea +that notes should follow a predictable and descriptive file-naming +scheme. The file name must offer a clear indication of what the note is +about, without reference to any other metadata. Denote basically +streamlines the creation of such files while providing facilities to +link between them. + +Denote's file-naming scheme is not limited to "notes". It can be used +for all types of file, including those that are not editable in Emacs, +such as videos. Naming files in a consistent way makes their +filtering and retrieval considerably easier. Denote provides relevant +facilities to rename files, regardless of file type. + ++ Package name (GNU ELPA): `denote` ++ Official manual: ++ Change log: ++ Git repositories: + + GitHub: + + GitLab: ++ Video demo: ++ Backronyms: Denote Everything Neatly; Omit The Excesses. Don't Ever + Note Only The Epiphenomenal. blob - /dev/null blob + 505300ca60d8d62af9aedbb0d466ed97860601d3 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/README.org @@ -0,0 +1,6808 @@ +#+title: denote: Simple notes with an efficient file-naming scheme +#+author: Protesilaos Stavrou +#+email: info@protesilaos.com +#+language: en +#+options: ':t toc:nil author:t email:t num:t +#+startup: content +#+macro: stable-version 4.0.0 +#+macro: release-date 2025-04-15 +#+macro: development-version 4.1.0-dev +#+export_file_name: denote.texi +#+texinfo_filename: denote.info +#+texinfo_dir_category: Emacs misc features +#+texinfo_dir_title: Denote: (denote) +#+texinfo_dir_desc: Simple notes with an efficient file-naming scheme +#+texinfo_header: @set MAINTAINERSITE @uref{https://protesilaos.com,maintainer webpage} +#+texinfo_header: @set MAINTAINER Protesilaos Stavrou +#+texinfo_header: @set MAINTAINEREMAIL @email{info@protesilaos.com} +#+texinfo_header: @set MAINTAINERCONTACT @uref{mailto:info@protesilaos.com,contact the maintainer} + +#+texinfo: @insertcopying + +This manual, written by Protesilaos Stavrou, describes the customization +options for the Emacs package called ~denote~ (or =denote.el=), and +provides every other piece of information pertinent to it. + +The documentation furnished herein corresponds to stable version +{{{stable-version}}}, released on {{{release-date}}}. Any reference to +a newer feature which does not yet form part of the latest tagged +commit, is explicitly marked as such. + +Current development target is {{{development-version}}}. + ++ Package name (GNU ELPA): ~denote~ ++ Official manual: ++ Change log: ++ Git repositories: + + GitHub: + + GitLab: ++ Video demo: ++ Backronyms: Denote Everything Neatly; Omit The Excesses. Don't Ever + Note Only The Epiphenomenal. + +If you are viewing the README.org version of this file, please note that +the GNU ELPA machinery automatically generates an Info manual out of it. + +#+toc: headlines 8 insert TOC here, with eight headline levels + +* COPYING +:PROPERTIES: +:COPYING: t +:CUSTOM_ID: h:40b18bb2-4dc1-4202-bd0b-6fab535b2a0f +:END: + +Copyright (C) 2022-2025 Free Software Foundation, Inc. + +#+begin_quote +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and +with the Back-Cover Texts as in (a) below. A copy of the license is +included in the section entitled “GNU Free Documentation License.” + +(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and +modify this GNU manual.” +#+end_quote + +* Installation +:PROPERTIES: +:CUSTOM_ID: h:f3bdac2c-4704-4a51-948c-a789a2589790 +:END: +#+cindex: Installation instructions + +** GNU ELPA package +:PROPERTIES: +:CUSTOM_ID: h:42953f87-82bd-43ec-ab99-22b1e22955e7 +:END: + +The package is available as =denote=. Simply do: + +: M-x package-refresh-contents +: M-x package-install + +And search for it. + +GNU ELPA provides the latest stable release. Those who prefer to follow +the development process in order to report bugs or suggest changes, can +use the version of the package from the GNU-devel ELPA archive. Read: +https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/. + +** Manual installation +:PROPERTIES: +:CUSTOM_ID: h:d397712c-c8c0-4cfa-ad1a-ef28cf78d1f0 +:END: + +Assuming your Emacs files are found in =~/.emacs.d/=, execute the +following commands in a shell prompt: + +#+begin_src sh +cd ~/.emacs.d + +# Create a directory for manually-installed packages +mkdir manual-packages + +# Go to the new directory +cd manual-packages + +# Clone this repo, naming it "denote" +git clone https://github.com/protesilaos/denote denote +#+end_src + +Finally, in your =init.el= (or equivalent) evaluate this: + +#+begin_src emacs-lisp +;; Make Elisp files in that directory available to the user. +(add-to-list 'load-path "~/.emacs.d/manual-packages/denote") +#+end_src + +Everything is in place to set up the package. + +* Sample configuration +:PROPERTIES: +:CUSTOM_ID: h:5d16932d-4f7b-493d-8e6a-e5c396b15fd6 +:END: +#+cindex: Package configuration + +Denote is immediately useful for beginners and power users alike. This +manual covers everything in detail, though do not let the numerous +possibilities distract you from the fact that a basic configuration is +enough to be highly productive ([[#h:998ae528-9276-47ec-b642-3d7355a38f27][Get started with this sample configuration]]). + +** Get started with this sample configuration +:PROPERTIES: +:CUSTOM_ID: h:998ae528-9276-47ec-b642-3d7355a38f27 +:END: + +If you are new to Denote, this a good place to start. Then work your +way through the manual and expand your configuration accordingly. Only +include commands/variables that are useful to you. We provide another +code sample if you need some ideas ([[#h:58c4746b-b0d8-4896-9d88-a99b1d487231][More comprehensive sample configuration]]). + +#+begin_src emacs-lisp +;; Remember that the website version of this manual shows the latest +;; developments, which may not be available in the package you are +;; using. Instead of copying from the web site, refer to the version +;; of the documentation that comes with your package. Evaluate: +;; +;; (info "(denote) Sample configuration") +(use-package denote + :ensure t + :hook (dired-mode . denote-dired-mode) + :bind + (("C-c n n" . denote) + ("C-c n r" . denote-rename-file) + ("C-c n l" . denote-link) + ("C-c n b" . denote-backlinks) + ("C-c n d" . denote-dired) + ("C-c n g" . denote-grep)) + :config + (setq denote-directory (expand-file-name "~/Documents/notes/")) + + ;; Automatically rename Denote buffers when opening them so that + ;; instead of their long file name they have, for example, a literal + ;; "[D]" followed by the file's title. Read the doc string of + ;; `denote-rename-buffer-format' for how to modify this. + (denote-rename-buffer-mode 1)) +#+end_src + +** More comprehensive sample configuration +:PROPERTIES: +:CUSTOM_ID: h:58c4746b-b0d8-4896-9d88-a99b1d487231 +:END: + +Here we include more of what you can configure with Denote ([[#h:998ae528-9276-47ec-b642-3d7355a38f27][Get started with this sample configuration]]). + +#+begin_src emacs-lisp +;; Remember that the website version of this manual shows the latest +;; developments, which may not be available in the package you are +;; using. Instead of copying from the web site, refer to the version +;; of the documentation that comes with your package. Evaluate: +;; +;; (info "(denote) Sample configuration") +(use-package denote + :ensure t + :hook + ( ;; If you use Markdown or plain text files, then you want to make + ;; the Denote links clickable (Org renders links as buttons right + ;; away) + (text-mode . denote-fontify-links-mode-maybe) + ;; Apply colours to Denote names in Dired. This applies to all + ;; directories. Check `denote-dired-directories' for the specific + ;; directories you may prefer instead. Then, instead of + ;; `denote-dired-mode', use `denote-dired-mode-in-directories'. + (dired-mode . denote-dired-mode)) + :bind + ;; Denote DOES NOT define any key bindings. This is for the user to + ;; decide. For example: + ( :map global-map + ("C-c n n" . denote) + ("C-c n d" . denote-dired) + ("C-c n g" . denote-grep) + ;; If you intend to use Denote with a variety of file types, it is + ;; easier to bind the link-related commands to the `global-map', as + ;; shown here. Otherwise follow the same pattern for `org-mode-map', + ;; `markdown-mode-map', and/or `text-mode-map'. + ("C-c n l" . denote-link) + ("C-c n L" . denote-add-links) + ("C-c n b" . denote-backlinks) + ("C-c n q c" . denote-query-contents-link) ; create link that triggers a grep + ("C-c n q f" . denote-query-filenames-link) ; create link that triggers a dired + ;; Note that `denote-rename-file' can work from any context, not just + ;; Dired bufffers. That is why we bind it here to the `global-map'. + ("C-c n r" . denote-rename-file) + ("C-c n R" . denote-rename-file-using-front-matter) + + ;; Key bindings specifically for Dired. + :map dired-mode-map + ("C-c C-d C-i" . denote-dired-link-marked-notes) + ("C-c C-d C-r" . denote-dired-rename-files) + ("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords) + ("C-c C-d C-R" . denote-dired-rename-marked-files-using-front-matter)) + + :config + ;; Remember to check the doc string of each of those variables. + (setq denote-directory (expand-file-name "~/Documents/notes/")) + (setq denote-save-buffers nil) + (setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) + (setq denote-infer-keywords t) + (setq denote-sort-keywords t) + (setq denote-prompts '(title keywords)) + (setq denote-excluded-directories-regexp nil) + (setq denote-excluded-keywords-regexp nil) + (setq denote-rename-confirmations '(rewrite-front-matter modify-file-name)) + + ;; Pick dates, where relevant, with Org's advanced interface: + (setq denote-date-prompt-use-org-read-date t) + + ;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. + (denote-rename-buffer-mode 1)) +#+end_src + +* Overview +:PROPERTIES: +:CUSTOM_ID: h:a09b70a2-ae0b-4855-ac14-1dddfc8e3241 +:END: + +Denote aims to be a simple-to-use, focused-in-scope, and effective +note-taking and file-naming tool for Emacs. + +Denote is based on the idea that files should follow a predictable and +descriptive file-naming scheme. The file name must offer a clear +indication of what the contents are about, without reference to any +other metadata. Denote basically streamlines the creation of such +files or file names while providing facilities to link between them +(where those files are editable). + +Denote's file-naming scheme is not limited to "notes". It can be used +for all types of file, including those that are not editable in Emacs, +such as videos. Naming files in a consistent way makes their +filtering and retrieval considerably easier. Denote provides relevant +facilities to rename files, regardless of file type. + +Denote is based on the following core design principles: + ++ Predictability :: File names must follow a consistent and descriptive + naming convention ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). The file name alone + should offer a clear indication of what the contents are, without + reference to any other metadatum. This convention is not specific to + note-taking, as it is pertinent to any form of file that is part of + the user's long-term storage ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). + ++ Composability :: Be a good Emacs citizen, by integrating with other + packages or built-in functionality instead of re-inventing functions + such as for filtering or greping. The author of Denote (Protesilaos, + aka "Prot") writes ordinary notes in plain text (=.txt=), switching on + demand to an Org file only when its expanded set of functionality is + required for the task at hand ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). + ++ Portability :: Notes are plain text and should remain portable. The + way Denote writes file names, the front matter it includes in the + note's header, and the links it establishes must all be adequately + usable with standard Unix tools. No need for a database or some + specialised software. As Denote develops and this manual is fully + fleshed out, there will be concrete examples on how to do the + Denote-equivalent on the command-line. + ++ Flexibility :: Do not assume the user's preference for a note-taking + methodology. Denote is conceptually similar to the Zettelkasten + Method, which you can learn more about in this detailed introduction: + . Notes are atomic (one file + per note) and have a unique identifier. However, Denote does not + enforce a particular methodology for knowledge management, such as a + restricted vocabulary or mutually exclusive sets of keywords. Denote + also does not check if the user writes thematically atomic notes. It + is up to the user to apply the requisite rigor and/or creativity in + pursuit of their preferred workflow ([[#h:6060a7e6-f179-4d42-a9de-a9968aaebecc][Writing metanotes]]). + ++ Hackability :: Denote's code base consists of small and reusable + functions. They all have documentation strings. The idea is to make + it easier for users of varying levels of expertise to understand what + is going on and make surgical interventions where necessary (e.g. to + tweak some formatting). In this manual, we provide concrete examples + on such user-level configurations ([[#h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92][Keep a journal or diary]]). + +Now the important part... "Denote" is the familiar word, though it also +is a play on the "note" concept. Plus, we can come up with acronyms, +recursive or otherwise, of increasingly dubious utility like: + ++ Don't Ever Note Only The Epiphenomenal ++ Denote Everything Neatly; Omit The Excesses + +But we'll let you get back to work. Don't Eschew or Neglect your +Obligations, Tasks, and Engagements. + +* Points of entry +:PROPERTIES: +:CUSTOM_ID: h:17896c8c-d97a-4faa-abf6-31df99746ca6 +:END: + +#+findex: denote +#+findex: denote-type +#+findex: denote-org-capture +#+findex: denote-date +#+findex: denote-subdirectory +#+findex: denote-template +#+findex: denote-signature +There are seven main ways to write a note with Denote: invoke the +~denote~, ~denote-type~, ~denote-date~, ~denote-subdirectory~, +~denote-template~, ~denote-signature~ commands, or leverage the +~org-capture-templates~ by setting up a template which calls the +function ~denote-org-capture~. We explain all of those in the +subsequent sections. Other more specialised commands exist as well, +which one shall learn about as they read through this manual. We do +not want to overwhelm the user with options at this stage. + +All these commands construct the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +** Standard note creation +:PROPERTIES: +:CUSTOM_ID: h:6a92a8b5-d766-42cc-8e5b-8dc255466a23 +:END: + +The ~denote~ command will prompt for a title. If a region is active, +the text of the region becomes the default at the minibuffer prompt +(meaning that typing =RET= without any input will use the default +value). Once the title is supplied, the ~denote~ command will then ask +for keywords. The resulting note will have a file name as already +explained: [[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file naming scheme]] + +#+vindex: denote-after-new-note-hook +The ~denote~ command runs the hook ~denote-after-new-note-hook~ after +creating the new note ([[#h:a947908e-1847-4471-ba07-377ee2f4b36c][Access the data of the latest note]]). When +called from Lisp, it returns the path it generates. Before returning +the path, it decides what to do with the buffer of the note, in +accordance with the user option ~denote-kill-buffers~ ([[#h:c8fd826f-3ac9-4820-9709-4375603f8865][The ~denote-kill-buffers~ option]]). + +The file type of the new note is determined by the user option +~denote-file-type~ ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]). + +#+vindex: denote-known-keywords +#+vindex: denote-infer-keywords +The keywords' prompt supports minibuffer completion. Available +candidates are those defined in the user option ~denote-known-keywords~. +More candidates can be inferred from the names of existing notes, by +setting ~denote-infer-keywords~ to non-nil (which is the case by +default) ([[#h:c0fb477f-4f99-4d76-9cce-132bcfcb351d][Create a controlled vocabulary for keywords]]). + +#+vindex: denote-sort-keywords +Multiple keywords can be inserted by separating them with a comma (or +whatever the value of the ~crm-separator~ is---which should be a comma). +When the user option ~denote-sort-keywords~ is non-nil (the default), +keywords are sorted alphabetically (technically, the sorting is done +with ~string-lessp~). + +The interactive behaviour of the ~denote~ command is influenced by the +user option ~denote-prompts~ ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The denote-prompts option]]). + +The ~denote~ command can also be called from Lisp. Read its doc string +for the technicalities. + +#+findex: denote-create-note +In the interest of discoverability, ~denote~ is also available under the +alias ~denote-create-note~. + +*** The ~denote-prompts~ option +:PROPERTIES: +:CUSTOM_ID: h:f9204f1f-fcee-49b1-8081-16a08a338099 +:END: + +#+vindex: denote-prompts +The user option ~denote-prompts~ determines how the ~denote~ command +will behave interactively ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). + +Commands that prompt for user input to construct a Denote file name +include, but are not limited to: ~denote~, ~denote-signature~, +~denote-type~, ~denote-date~, ~denote-subdirectory~, +~denote-rename-file~, ~denote-dired-rename-files~. + +- [[#h:887bdced-9686-4e80-906f-789e407f2e8f][Convenience commands for note creation]]. +- [[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]. + +The value of this user option is a list of symbols, which includes any +of the following: + +- =title=: Prompt for the title of the new note ([[#h:403422a7-7578-494b-8f33-813874c12da3][The ~denote-history-completion-in-prompts~ option]]). + +- =keywords=: Prompts with completion for the keywords of the new note. + Available candidates are those specified in the user option + ~denote-known-keywords~. If the user option ~denote-infer-keywords~ + is non-nil, keywords in existing note file names are included in the + list of candidates. The =keywords= prompt uses + ~completing-read-multiple~, meaning that it can accept multiple + keywords separated by a comma (or whatever the value of ~crm-separator~ + is). + +- =file-type=: Prompts with completion for the file type of the new + note. Available candidates are those specified in the user option + ~denote-file-type~. Without this prompt, ~denote~ uses the value of + ~denote-file-type~. + +- =subdirectory=: Prompts with completion for a subdirectory in which to + create the note. Available candidates are the value of the user + option ~denote-directory~ and all of its subdirectories. Any + subdirectory must already exist: Denote will not create it. + +- =date=: Prompts for the date of the new note. It will expect an input + like 2022-06-16 or a date plus time: 2022-06-16 14:30. Without the + =date= prompt, the ~denote~ command uses the ~current-time~. + + [[#h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63][The denote-date-prompt-use-org-read-date option]]. + +- =template=: Prompts for a KEY among the ~denote-templates~. The value + of that KEY is used to populate the new note with content, which is + added after the front matter ([[#h:f635a490-d29e-4608-9372-7bd13b34d56c][The denote-templates option]]). + +- =signature=: - Prompts for an arbitrary string that can be used for + any kind of workflow, such as a special tag to label the =part1= and + =part2= of a large file that is split in half, or to add special + contexts like =home= and =work=, or even priorities like =a=, =b=, + =c=. One other use-case is to implement a sequencing scheme that + makes notes have hierarchical relationships. This is handled by our + optional extension =denote-sequence.el=, which is part of the + ~denote~ package ([[#h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f][Write sequence notes or "folgezettel"]]). + +The prompts occur in the given order. + +If the value of this user option is nil, no prompts are used. The +resulting file name will consist of an identifier (i.e. the date and +time) and a supported file type extension (per ~denote-file-type~). + +Recall that Denote's standard file-naming scheme is defined as follows +([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]): + +: DATE--TITLE__KEYWORDS.EXT + +If either or both of the =title= and =keywords= prompts are not +included in the value of this variable, file names will be any of +those permutations: + +: DATE.EXT +: DATE--TITLE.EXT +: DATE__KEYWORDS.EXT + +When in doubt, always include the =title= and =keywords= prompts. + +Finally, this user option only affects the interactive use of the +~denote~ or other relevant commands (advanced users can call it from +Lisp). In Lisp usage, the behaviour is always what the caller +specifies, based on the supplied arguments. + +*** The ~denote-history-completion-in-prompts~ option +:PROPERTIES: +:CUSTOM_ID: h:403422a7-7578-494b-8f33-813874c12da3 +:END: + +#+vindex: denote-history-completion-in-prompts +The user option ~denote-history-completion-in-prompts~ toggles history +completion in all ~denote-prompts-with-history-as-completion~. + +When this user option is set to a non-nil value, Denote will use +minibuffer history entries as completion candidates in all of the +~denote-prompts-with-history-as-completion~. Those will show previous +inputs from their respective history as possible values to select, +either to (i) re-insert them verbatim or (ii) with the intent to edit +them further (depending on the minibuffer user interface, one can +select a candidate with =TAB= without exiting the minibuffer, as +opposed to what =RET= normally does by selecting and exiting). + +When this user option is set to a nil value, all of the +~denote-prompts-with-history-as-completion~ will not use minibuffer +completion: they will just prompt for a string of characters. Their +history is still available through all the standard ways of retrieving +minibuffer history, such as with the command ~previous-history-element~. + +History completion still allows arbitrary values to be provided as +input: they do not have to match the available minibuffer completion +candidates. + +Note that some prompts, like ~denote-keywords-prompt~, always use +minibuffer completion, due to the specifics of their data. + +[ Consider enabling the built-in ~savehist-mode~ to persist minibuffer + histories between sessions.] + +*** The ~denote-templates~ option +:PROPERTIES: +:CUSTOM_ID: h:f635a490-d29e-4608-9372-7bd13b34d56c +:END: + +#+vindex: denote-templates +The user option ~denote-templates~ is an alist of content templates for +new notes. A template is arbitrary text that Denote will add to a newly +created note right below the front matter. + +Templates are expressed as a =(KEY . VALUE)= association. + +- The =KEY= is the name which identifies the template. It is an + arbitrary symbol, such as =report=, =memo=, =statement=. + +- The =VALUE= is either a string or the symbol of a function. + + - If it is a string, it is ordinary text that Denote will insert + as-is. It can contain newline characters to add spacing. The + manual of Denote contains examples on how to use the ~concat~ + function, beside writing a generic string. + + - If it is a function, it is called without arguments and is expected + to return a string. Denote will call the function and insert the + result in the buffer. + +The user can choose a template either by invoking the command +~denote-template~ or by changing the user option ~denote-prompts~ to +always prompt for a template when calling the ~denote~ command. + +[[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The denote-prompts option]]. + +[[#h:887bdced-9686-4e80-906f-789e407f2e8f][Convenience commands for note creation]]. + +Templates can be written directly as one large string. For example (the +=\n= character is read as a newline): + +#+begin_src emacs-lisp +(setq denote-templates + '((report . "* Some heading\n\n* Another heading") + (memo . "* Some heading + +,* Another heading + +"))) +#+end_src + +Long strings may be easier to type but interpret indentation literally. +Also, they do not scale well. A better way is to use some Elisp code to +construct the string. This would typically be the ~concat~ function, +which joins multiple strings into one. The following is the same as the +previous example: + +#+begin_src emacs-lisp +(setq denote-templates + `((report . "* Some heading\n\n* Another heading") + (memo . ,(concat "* Some heading" + "\n\n" + "* Another heading" + "\n\n")))) +#+end_src + +Notice that to evaluate a function inside of an alist we use the +backtick to quote the alist (NOT the straight quote) and then prepend a +comma to the expression that should be evaluated. The ~concat~ form +here is not sensitive to indentation, so it is easier to adjust for +legibility. + +For when the =VALUE= is a function, we have this: + +#+begin_src emacs-lisp +(setq denote-templates + `((report . "* Some heading\n\n* Another heading") + (blog . my-denote-template-function-for-blog) ; a function to return a string + (memo . ,(concat "* Some heading" + "\n\n" + "* Another heading" + "\n\n")))) +#+end_src + +In this example, ~my-denote-template-function-for-blog~ is a function +that returns a string. Denote will take care to insert it in the buffer. + +DEV NOTE: We do not provide more examples at this point, though feel +welcome to ask for help if the information provided herein is not +sufficient. We shall expand the manual accordingly. + +*** Convenience commands for note creation +:PROPERTIES: +:CUSTOM_ID: h:887bdced-9686-4e80-906f-789e407f2e8f +:END: + +Sometimes the user needs to create a note that has different +requirements from those of ~denote~ ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). While +this can be achieved globally by changing the ~denote-prompts~ user +option, there are cases where an ad-hoc method is the appropriate one +([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The denote-prompts option]]). + +To this end, Denote provides the following interactive convenience +commands for note creation. They all work by appending a new prompt to +the existing ~denote-prompts~. + ++ Create note by specifying file type :: The ~denote-type~ command + creates a note while prompting for a file type. + + This is the equivalent of calling ~denote~ when ~denote-prompts~ has + the =file-type= prompt appended to its existing prompts. In practical + terms, this lets you produce, say, a note in Markdown even though + you normally write in Org ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). + + #+findex: denote-create-note-using-type + The ~denote-create-note-using-type~ is an alias of ~denote-type~. + ++ Create note using a date :: Normally, Denote reads the current date + and time to construct the unique identifier of a newly created note + ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). Sometimes, however, the user needs to set + an explicit date+time value. + + This is where the ~denote-date~ command comes in. It creates a note + while prompting for a date. The date can be in YEAR-MONTH-DAY + notation like =2022-06-30= or that plus the time: =2022-06-16 14:30=. + + [[#h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63][The denote-date-prompt-use-org-read-date option]]. + + This is the equivalent of calling ~denote~ when ~denote-prompts~ has + the =date= prompt appended to its existing prompts. + + #+findex: denote-create-note-using-date + The ~denote-create-note-using-date~ is an alias of ~denote-date~. + ++ Create note in a specific directory :: The ~denote-subdirectory~ + command creates a note while prompting for a subdirectory. Available + candidates include the value of the variable ~denote-directory~ and + any subdirectory thereof (Denote does not create subdirectories). + + This is the equivalent of calling ~denote~ when ~denote-prompts~ has + the =subdirectory= prompt appended to its existing prompts. + + #+findex: denote-create-note-in-subdirectory + The ~denote-create-note-in-subdirectory~ is a more descriptive alias + of ~denote-subdirectory~. + ++ Create note and add a template :: The ~denote-template~ command + creates a new note and inserts the specified template below the front + matter ([[#h:f635a490-d29e-4608-9372-7bd13b34d56c][The denote-templates option]]). Available candidates for + templates are specified in the user option ~denote-templates~. + + This is the equivalent of calling ~denote~ when ~denote-prompts~ has + the =template= prompt appended to its existing prompts. + + #+findex: denote-create-note-with-template + The ~denote-create-note-with-template~ is an alias of the command + ~denote-template~, meant to help with discoverability. + ++ Create note with a signature :: The ~denote-signature~ command first + prompts for an arbitrary string to use in the optional =SIGNATURE= + field of the file name and then asks for a title and keywords. + Signatures are arbitrary strings of alphanumeric characters which + can be used to establish sequential relations between file at the + level of their file name (e.g. 1, 1a, 1b, 1b1, 1b2, ...). + + This is the equivalent of calling ~denote~ when ~denote-prompts~ has + the =signature= prompt appended to its existing prompts. + + The ~denote-create-note-using-signature~ is an alias of the command + ~denote-signature~ intended to make the functionality more + discoverable. + +**** Write your own convenience commands +:PROPERTIES: +:CUSTOM_ID: h:11946562-7eb0-4925-a3b5-92d75f1f5895 +:END: + +The convenience commands we provide only cover some basic use-cases +([[#h:887bdced-9686-4e80-906f-789e407f2e8f][Convenience commands for note creation]]). The user may require +combinations that are not covered, such as to prompt for a template +and for a subdirectory, instead of only one of the two. To this end, +we show how to follow the code we use in Denote to write your own +variants of those commands. + +First let's take a look at the definition of one of those commands. +They all look the same, but we use ~denote-subdirectory~ for this +example: + +#+begin_src emacs-lisp +(defun denote-subdirectory () + "Create note while prompting for a subdirectory. + +Available candidates include the value of the variable +`denote-directory' and any subdirectory thereof. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `subdirectory' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(subdirectory)))) + (call-interactively #'denote))) +#+end_src + +The hyphenated word after ~defun~ is the name of the function. It has +to be unique. Then we have the documentation string (or "doc string") +which is for the user's convenience. + +This function is ~interactive~, meaning that it can be called via +=M-x= or be assigned to a key binding. Then we have the local binding +of the ~denote-prompts~ to the desired combination ("local" means +specific to this function without affecting other contexts). Lastly, +it calls the standard ~denote~ command interactively, so it uses all +the prompts in their specified order. + +The function call ~(denote-add-prompts '(subdirectory))~ will append +the subdirectory prompt to the existing value of the ~denote-prompts~. +If, for example, the default value is ='(title keywords)= (to prompt +for a title and then for keywords), it will become ='(subdirectory +title keywords)= inside the context of this ~let~. Remember that this +is "local", so the global value of ~denote-prompts~ remains unaffected. + +Now let's say we want to have a command that (i) asks for a template +(ii) for a subdirectory ([[#h:f635a490-d29e-4608-9372-7bd13b34d56c][The denote-templates option]]), and (iii) then +goes through the remaining ~denote-prompts~. All we need to do is +tweak the ~let~ bound value of ~denote-prompts~ and give our command a +unique name: + +#+begin_src emacs-lisp +;; Like `denote-subdirectory' but also ask for a template +(defun my-denote-subdirectory-with-template () + "Create note while also prompting for a template and subdirectory. + +This is the equivalent of calling `denote' when `denote-prompts' has the +`subdirectory' and `template' prompts appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(subdirectory template)))) + (call-interactively #'denote))) +#+end_src + +The tweaks to ~denote-prompts~ determine how the command will behave +([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The denote-prompts option]]). Use this paradigm to write your own +variants which you can then assign to keys, invoke with =M-x=, or add +to the list of commands available at the ~denote-command-prompt~ +([[#h:98c732ac-da0e-4ebd-a0e3-5c47f9075e51][Choose which commands to prompt for]]). + +In the above scenario, we are using the ~denote-add-prompts~ function, +which appends whatever prompts we want to the existing value of +~denote-prompts~. If the user prefers to completely override the +~denote-prompts~, they can set the value outright: + +#+begin_src emacs-lisp +(defun my-denote-subdirectory-with-template-title-and-keywords () + "Create a note while prompting for subdirectory, template, title, and keywords. + +This is the equivalent of calling `denote' when `denote-prompts' has the +value '(template subdirectory title keywords)." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(subdirectory template title keywords))) + (call-interactively #'denote))) +#+end_src + +*** The ~denote-save-buffers~ option +:PROPERTIES: +:CUSTOM_ID: h:bf80f4cd-6f56-4f7c-a991-8573161e4511 +:END: + +#+vindex: denote-save-buffers +The user option ~denote-save-buffer-after-creation~ controls whether +commands that create new notes save their buffer outright. + +The default behaviour of commands such as ~denote~ (or related) is to +not save the buffer they create ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). This gives the user +the chance to review the text before writing it to a file. The user +may choose to delete the unsaved buffer, thus not creating a new note +([[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffer-after-creation~ option]]). + +This option also applies to notes affected by the renaming commands +(~denote-rename-file~ and related). + +If this user option is set to a non-nil value, such buffers are saved +automatically. The assumption is that the user who opts in to this +feature is familiar with the ~denote-rename-file~ operation (or +related) and knows it is reliable ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). + +[[#h:c8fd826f-3ac9-4820-9709-4375603f8865][The ~denote-kill-buffers~ option]]. + +*** The ~denote-kill-buffers~ option +:PROPERTIES: +:CUSTOM_ID: h:c8fd826f-3ac9-4820-9709-4375603f8865 +:END: + +#+vindex: denote-kill-buffers +The user option ~denote-kill-buffers~ controls whether to kill a +buffer that was generated by a Denote command. This can happen when +creating a new file or renaming an existing one. + +- [[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]. +- [[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]. + +The default behaviour of creation or renaming commands such as +~denote~ or ~denote-rename-file~ is to not kill the buffer they +create or modify at the end of their operation. The idea is to give +the user the chance to confirm that everything is in order. + +If this user option is nil (the default), buffers affected by a +creation or renaming command are not automatically killed. + +If set to the symbol =on-creation=, new notes are automatically killed. + +If set to the symbol =on-rename=, renamed notes are automatically +killed. + +If set to t, new and renamed notes are killed. + +If a buffer is killed, it is also saved, as if ~denote-save-buffers~ +were t ([[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffers~ option]]). + +In all cases, if the buffer already existed before the Denote operation +it is NOT automatically killed. + +*** The ~denote-date-prompt-use-org-read-date~ option +:PROPERTIES: +:CUSTOM_ID: h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63 +:END: + +By default, Denote uses its own simple prompt for date or date+time +input ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The denote-prompts option]]). This is done when the +~denote-prompts~ option includes a =date= symbol and/or when the user +invokes the ~denote-date~ command. + +#+vindex: denote-date-prompt-use-org-read-date +Users who want to benefit from the more advanced date selection method +that is common in interactions with Org mode, can set the user option +~denote-date-prompt-use-org-read-date~ to a non-nil value. + + +** Create note using Org capture +:PROPERTIES: +:CUSTOM_ID: h:656c70cd-cf9a-4471-a0b5-4f0aaf60f881 +:END: + +For integration with ~org-capture~, the user must first add the relevant +template. Such as: + +#+begin_src emacs-lisp +(with-eval-after-load 'org-capture + (add-to-list 'org-capture-templates + '("n" "New note (with Denote)" plain + (file denote-last-path) + #'denote-org-capture + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t))) +#+end_src + +Once the template is added, it is accessed from the specified key. If, +for instance, ~org-capture~ is bound to =C-c c=, then the note +creation is initiated with =C-c c n=, per the above snippet. After +that, the process is the same as with invoking ~denote~ directly, +namely: a prompt for a title followed by a prompt for keywords, +assuming the default settings ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). Concretely, +this method always respects the value of the user option +~denote-prompts~ ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The ~denote-prompts~ option]]). + +It is also possible to define templates that have specific prompts or +certain values set, for which there is no prompt: + +- [[#h:115b6797-f265-40e9-a603-32eeda13a7ac][Create note with specific values using Org capture]] +- [[#h:95b78582-9086-47e8-967f-62373e2369a0][Create note with specific prompts using Org capture]] + +#+vindex: denote-org-capture-specifiers +Users may prefer to leverage ~org-capture~ in order to extend file +creation with the specifiers described in the ~org-capture-templates~ +documentation (such as to capture the active region and/or create a +hyperlink pointing to the given context). + +IMPORTANT. Due to the particular file-naming scheme of Denote, which is +derived dynamically, such specifiers or other arbitrary text cannot be +written directly in the template. Instead, they have to be assigned to +the user option ~denote-org-capture-specifiers~, which is interpreted by +the function ~denote-org-capture~. Example with our default value: + +#+begin_src emacs-lisp +(setq denote-org-capture-specifiers "%l\n%i\n%?") +#+end_src + +Note that ~denote-org-capture~ ignores the ~denote-file-type~: it always +sets the Org file extension for the created note to ensure that the +capture process works as intended, especially for the desired output of +the ~denote-org-capture-specifiers~. + +[ You may not need ~org-capture~ to do what you want ([[#h:11946562-7eb0-4925-a3b5-92d75f1f5895][Write your own convenience commands]]). ] + +** Create note with specific prompts using Org capture +:PROPERTIES: +:CUSTOM_ID: h:95b78582-9086-47e8-967f-62373e2369a0 +:END: + +This section assumes knowledge of how Denote+org-capture work, as +explained in the previous section ([[#h:656c70cd-cf9a-4471-a0b5-4f0aaf60f881][Create note using Org capture]]). + +#+findex: denote-org-capture-with-prompts +The previous section shows how to define an Org capture template that +always prompts for whatever is set in the user option ~denote-prompts~ +(title and keywords, by default). There are, however, cases where the +user wants more control over what kind of input Denote will prompt +for. To this end, we provide the function ~denote-org-capture-with-prompts~. +Below we explain it and then show some examples of how to use it. + +The ~denote-org-capture-with-prompts~ is like ~denote-org-capture~ but +with optional prompt parameters. + +When called without arguments, it does not prompt for anything. It +just returns the front matter with title and keyword fields empty and +the date and identifier fields specified. It also makes the file name +consist of only the identifier plus the Org file name extension ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +Otherwise, it produces a minibuffer prompt for every non-nil value +that corresponds to the =TITLE=, =KEYWORDS=, =SUBDIRECTORY=, =DATE=, +and =TEMPLATE= arguments. The prompts are those used by the standard +~denote~ command and all of its utility commands ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). + +When returning the contents that fill in the Org capture template, the +sequence is as follows: front matter, =TEMPLATE=, and then the value +of the user option ~denote-org-capture-specifiers~. + +Important note: in the case of =SUBDIRECTORY= actual subdirectories +must exist---Denote does not create them. Same principle for +=TEMPLATE= as templates must exist and are specified in the user +option ~denote-templates~. + +This is how one can incorporate ~denote-org-capture-with-prompts~ in +their Org capture templates. Instead of passing a generic ~t~ which +makes it hard to remember what the argument means, we use semantic +keywords like =:title= for our convenience (internally this does not +matter as the value still counts as non-nil, so =:foo= for =TITLE= is +treated the same as =:title= or ~t~). + +#+begin_src emacs-lisp +;; This prompts for TITLE, KEYWORDS, and SUBDIRECTORY +(add-to-list 'org-capture-templates + '("N" "New note with prompts (with denote.el)" plain + (file denote-last-path) + (function + (lambda () + (denote-org-capture-with-prompts :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t)) + +;; This prompts only for SUBDIRECTORY +(add-to-list 'org-capture-templates + '("N" "New note with prompts (with denote.el)" plain + (file denote-last-path) + (function + (lambda () + (denote-org-capture-with-prompts nil nil :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t)) + +;; This prompts for TITLE and SUBDIRECTORY +(add-to-list 'org-capture-templates + '("N" "New note with prompts (with denote.el)" plain + (file denote-last-path) + (function + (lambda () + (denote-org-capture-with-prompts :title nil :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t)) +#+end_src + +[ You may not need ~org-capture~ to do what you want ([[#h:11946562-7eb0-4925-a3b5-92d75f1f5895][Write your own convenience commands]]). ] + +** Create note with specific values using Org capture +:PROPERTIES: +:CUSTOM_ID: h:115b6797-f265-40e9-a603-32eeda13a7ac +:END: + +The ordinary procedure to create a note with ~org-capture~ respects +the value of the user option ~denote-prompts~ ([[#h:656c70cd-cf9a-4471-a0b5-4f0aaf60f881][Create note using Org capture]]): +the user is prompted for all the values they have configured (title +and keywords, by default). Sometimes, there is no need to have a +certain prompt because the value of it will be constant. For example, +the user wants to have a template that (i) respects the +~denote-prompts~ but (ii) puts the new note in an existing subdirectory +of the ~denote-directory~. The following code block does exactly that. + +[ It also is possible to have a template that deviates from + ~denote-prompts~ and prompts for specific values ([[#h:95b78582-9086-47e8-967f-62373e2369a0][Create note with specific prompts using Org capture]]). ] + +#+begin_src emacs-lisp +(with-eval-after-load 'org-capture + (add-to-list 'org-capture-templates + '("r" "New reference (with Denote)" plain + (file denote-last-path) + (function + (lambda () + (let ((denote-use-directory (expand-file-name "reference" (denote-directory)))) + (denote-org-capture)))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t))) +#+end_src + +The values one may predefine in this way are via these variables ([[#h:c916d8c5-540a-409f-b780-6ccbd90e088e][For developers or advanced users]]): + +#+findex: denote-use-date ++ ~denote-use-date~ + +#+findex: denote-use-directory ++ ~denote-use-directory~ + +#+findex: denote-use-file-type ++ ~denote-use-file-type~ + +#+findex: denote-use-keywords ++ ~denote-use-keywords~ + +#+findex: denote-use-signature ++ ~denote-use-signature~ + +#+findex: denote-use-template ++ ~denote-use-template~ + +#+findex: denote-use-title ++ ~denote-use-title~ + +When there exists a binding for the aforementioned variables, the +corresponding prompt is always skipped. It is thus paramount to never +set those variables outside the scope of a ~let~ (or equivalent). + +With those granted, here is another example scenario where the user +wants to have a constant value for the subdirectory but also be +prompted for a date. + +#+begin_src emacs-lisp +(with-eval-after-load 'org-capture + (add-to-list 'org-capture-templates + '("j" "New journal (with Denote)" plain + (file denote-last-path) + (function + (lambda () + ;; The "journal" subdirectory of the `denote-directory'---this must exist! + (let* ((denote-use-directory (expand-file-name "journal" (denote-directory))) + ;; Use the existing `denote-prompts' as well as the one for a date. + (denote-prompts (denote-add-prompts '(date)))) + (denote-org-capture)))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t))) +#+end_src + +The above highlights the hackability of the Denote code base, namely, +how we can affect the behaviour of the underlying ~denote~ command by +~let~ binding variables that affect every aspect of its behaviour +([[#h:11946562-7eb0-4925-a3b5-92d75f1f5895][Write your own convenience commands]]). + + +** Create a note with the region's contents +:PROPERTIES: +:CUSTOM_ID: h:2f8090f1-50af-4965-9771-d5a91a0a87bd +:END: + +#+findex: denote-region +The command ~denote-region~ takes the contents of the active region +and then calls the ~denote~ command. Once a new note is created, it +inserts the contents of the region therein. This is useful to +quickly elaborate on some snippet of text or capture it for future +reference. + +#+vindex: denote-region-after-new-note-functions +When the ~denote-region~ command is called with an active region, it +finalises its work by calling ~denote-region-after-new-note-functions~. +This is an abnormal hook, meaning that the functions added to it are +called with arguments. The arguments are two, representing the +beginning and end positions of the newly inserted text. + +A common use-case for Org mode users is to call the command +~org-insert-structure-template~ after a region is inserted. Emacs +will thus prompt for a structure template, such as the one +corresponding to a source block. In this case the function added to +~denote-region-after-new-note-functions~ does not actually need +aforementioned arguments: it can simply declare those as ignored by +prefixing the argument names with an underscore (an underscore is +enough, but it is better to include a name for clarity). For example, +the following will prompt for a structure template as soon as +~denote-region~ is done: + +#+begin_src emacs-lisp +(defun my-denote-region-org-structure-template (_beg _end) + (when (derived-mode-p 'org-mode) + (activate-mark) + (call-interactively 'org-insert-structure-template))) + +(add-hook 'denote-region-after-new-note-functions #'my-denote-region-org-structure-template) +#+end_src + +Remember that ~denote-region-after-new-note-functions~ are not called +if ~denote-region~ is used without an active region. + +*** A custom ~denote-region~ that references the source +:PROPERTIES: +:CUSTOM_ID: h:eb72086e-05be-4ae3-af51-7616999fc7c9 +:END: + +The ~denote-region~ command simply creates a new note and includes the +highlighted region's contents as the initial text of the note ([[#h:2f8090f1-50af-4965-9771-d5a91a0a87bd][Create a note with the region's contents]]). +However, users may want a more streamlined workflow where the command +is always used to capture quotes from other sources. In this example, +we consider "other sources" to come from Emacs EWW buffers (with ~M-x +eww~) or regular files outside the ~denote-directory~. + +[ This is a proof-of-concept that does not cover all cases. If anyone + wants to use a variation of this, just let me know. ] + +#+begin_src emacs-lisp +;; Variant of `my-denote-region' to reference the source + +(defun my-denote-region-get-source-reference () + "Get a reference to the source for use with `my-denote-region'. +The reference is a URL or an Org-formatted link to a file." + ;; We use a `cond' here because we can extend it to cover move + ;; cases. + (cond + ((derived-mode-p 'eww-mode) + (plist-get eww-data :url)) + ;; Here we are just assuming an Org format. We can make this more + ;; involved, if needed. + (buffer-file-name + (format "[[file:%s][%s]]" buffer-file-name (buffer-name))))) + +(defun my-denote-region () + "Like `denote-region', but add the context afterwards. +For how the context is retrieved, see `my-denote-region-get-source-reference'." + (interactive) + (let ((context (my-denote-region-get-source-reference))) + (call-interactively 'denote-region) + (when context + (goto-char (point-max)) + (insert "\n") + (insert context)))) + +;; Add quotes around snippets of text captured with `denote-region' or `my-denote-region'. + +(defun my-denote-region-org-structure-template (beg end) + "Automatically quote (with Org syntax) the contents of `denote-region'." + (when (derived-mode-p 'org-mode) + (goto-char end) + (insert "#+end_quote\n") + (goto-char beg) + (insert "#+begin_quote\n"))) + +(add-hook 'denote-region-after-new-note-functions #'my-denote-region-org-structure-template) +#+end_src + +With the above in place, calling the ~my-denote-region~ command does +the following: + +- It creates a new note as usual, prompting for the relevant data. +- Inserts the contents of the region below the front matter of the new + note. +- Adds Org-style quotation block markers around the inserted region. +- Adds a link to the URL or file from where ~my-denote-region~ was called. + +** Open an existing note or create it if missing +:PROPERTIES: +:CUSTOM_ID: h:ad91ca39-cf10-4e16-b224-fdf78f093883 +:END: + +#+findex: denote-open-or-create +#+findex: denote-open-or-create-with-command +Sometimes it is necessary to briefly interrupt the ongoing writing +session to open an existing note or, if that is missing, to create it. +This happens when a new tangential thought occurs and the user wants +to confirm that an entry for it is in place. To this end, Denote +provides the command ~denote-open-or-create~ as well as its more +flexible counterpart ~denote-open-or-create-with-command~. + +The ~denote-open-or-create~ prompts to visit a file in the +~denote-directory~. At this point, the user must type in search terms +that match a file name. If the input does not return any matches and +the user confirms their choice to proceed (usually by typing RET +twice, depending on the minibuffer settings), ~denote-open-or-create~ +will call the ~denote~ command interactively to create a new note. It +will then use whatever prompts ~denote~ normally has, per the user +option ~denote-prompts~ ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). If the title prompt +is involved (the default behaviour), the ~denote-open-or-create~ sets +up this prompt to have the previous input as the default title of the +note to-be-created. This means that the user can type RET at the +empty prompt to re-use what they typed in previously. Commands to use +previous inputs from the history are also available (=M-p= or =M-n= in +the minibuffer, which call ~previous-history-element~ and +~next-history-element~ by default). Accessing the history is helpful +to, for example, make further edits to the available text. + +The ~denote-open-or-create-with-command~ is like the above, except +when it is about to create the new note it first prompts for the +specific file-creating command to use ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). For example, +the user may want to specify a signature for this new file, so they +can select the ~denote-signature~ command. + +Denote provides similar functionality for linking to an existing note +or creating a new one ([[#h:b6056e6b-93df-4e6b-a778-eebd105bac46][Link to a note or create it if missing]]). + +** Maintain separate directory silos for notes +:PROPERTIES: +:CUSTOM_ID: h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5 +:END: +#+cindex: Note silos + +The user option ~denote-directory~ accepts a value that represents the +path to a directory, such as =~/Documents/notes=. Normally, the user +will have one place where they store all their notes, in which case +this arrangement shall suffice. + +There is, however, the possibility to maintain separate directories of +notes. By "separate", we mean that they do not communicate with each +other: no linking between them, no common keywords, nothing. Think of +the scenario where one set of notes is for private use and another is +for an employer. We call these separate directories "silos". + +To create silos, the user must specify a local variable at the root of +the desired directory. This is done by creating a =.dir-locals.el= +file, with the following contents: + +#+begin_src emacs-lisp +;;; Directory Local Variables. For more information evaluate: +;;; +;;; (info "(emacs) Directory Variables") + +((nil . ((denote-directory . "/path/to/silo/")))) +#+end_src + +When inside the directory that contains this =.dir-locals.el= file, +all Denote commands/functions for note creation, linking, the +inference of available keywords, et cetera will use the silo as their +point of reference ([[#h:e43baf95-f201-4fec-8620-c0eb5eaa1c85][The ~denote-silo~ package which formerly was =denote-silo-extras.el=]]). +They will not read the global value of ~denote-directory~. The global +value of ~denote-directory~ is read everywhere else except the silos. + +In concrete terms, this is a representation of the directory structures +(notice the =.dir-locals.el= file is needed only for the silos): + +#+begin_example +;; This is the global value of 'denote-directory' (no need for a .dir-locals.el) +~/Documents/notes +|-- 20210303T120534--this-is-a-test__journal_philosophy.txt +|-- 20220303T120534--another-sample__journal_testing.md +`-- 20220620T181255--the-third-test__keyword.org + +;; A silo with notes for the employer +~/different/path/to/notes-for-employer +|-- .dir-locals.el +|-- 20210303T120534--this-is-a-test__conference.txt +|-- 20220303T120534--another-sample__meeting.md +`-- 20220620T181255--the-third-test__keyword.org + +;; Another silo with notes for my volunteering +~/different/path/to/notes-for-volunteering +|-- .dir-locals.el +|-- 20210303T120534--this-is-a-test__activism.txt +|-- 20220303T120534--another-sample__teambuilding.md +`-- 20220620T181255--the-third-test__keyword.org +#+end_example + +It is possible to configure other user options of Denote to have a +silo-specific value. For example, this one changes the +~denote-known-keywords~ only for this particular silo: + +#+begin_src emacs-lisp +;;; Directory Local Variables. For more information evaluate: +;;; +;;; (info "(emacs) Directory Variables") + +((nil . ((denote-directory . "/path/to/silo/") + (denote-known-keywords . ("food" "drink"))))) +#+end_src + +This one is like the above, but also disables ~denote-infer-keywords~: + +#+begin_src emacs-lisp +;;; Directory Local Variables. For more information evaluate: +;;; +;;; (info "(emacs) Directory Variables") + +((nil . ((denote-directory . "/path/to/silo/") + (denote-known-keywords . ("food" "drink")) + (denote-infer-keywords . nil)))) +#+end_src + +To expand the list of local variables to, say, cover specific major +modes, we can do something like this: + +#+begin_src emacs-lisp +;;; Directory Local Variables. For more information evaluate: +;;; +;;; (info "(emacs) Directory Variables") + +((nil . ((denote-directory . "/path/to/silo/") + (denote-known-keywords . ("food" "drink")) + (denote-infer-keywords . nil))) + (org-mode . ((org-hide-emphasis-markers . t) + (org-hide-macro-markers . t) + (org-hide-leading-stars . t)))) +#+end_src + +As not all user options have a "safe" local value, Emacs will ask the +user to confirm their choice and to store it in the Custom code +snippet that is normally appended to init file (or added to the file +specified by the user option ~custom-file~). + +Finally, it is possible to have a =.dir-locals.el= for subdirectories +of any ~denote-directory~. Perhaps to specify a different set of +known keywords, while not making the subdirectory a silo in its own +right. We shall not expand on such an example, as we trust the user +to experiment with the best setup for their workflow. + +Feel welcome to ask for help if the information provided herein is not +sufficient. The manual shall be expanded accordingly. + +*** Make Org export work with silos +:PROPERTIES: +:CUSTOM_ID: h:fed09992-7c43-4237-b48f-f654bc29d1d8 +:END: + +The Org export infrastructure is designed to ignore directory-local +variables. This means that Denote silos, which depend on setting the +local value of the variable ~denote-directory~, do not work as +intended ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]). More +specifically, the Denote links do not resolve to the right file, +because their path is changed during the export process. + +I brought this to the attention of the Org maintainer. The guidance +from their side is to use the =#+bind= keyword to specify a local +value for the ~denote-directory~: . +The prerequisite is to set ~org-export-allow-bind-keywords~ to a +non-nil value: + +#+begin_src emacs-lisp +(setq org-export-allow-bind-keywords t) +#+end_src + +I do not think this is an elegant solution, but here are two possible +ways to go about it, anyway: + +1. Manually add the =#+bind= keyword to each file you want to export. + It has to be like this: + + #+begin_src emacs-lisp + ,#+bind: denote-directory "/path/to/silo/" + #+end_src + +2. Alternatively, you can make the Org front matter that Denote uses + for new files automatically include the =#+bind= keyword with its + desired value. Here is a complete =.dir-locals.el= which (i) + defines the silo and (ii) modifies the ~denote-org-front-matter~ + accordingly: + + #+begin_src emacs-lisp + ;;; Directory Local Variables. For more information evaluate: + ;;; + ;;; (info "(emacs) Directory Variables") + + ((nil . ((denote-directory . "/path/to/silo/") + (denote-org-front-matter . + "#+title: %s +,#+date: %s +,#+filetags: %s +,#+identifier: %s +,#+bind: denote-directory \"/path/to/silo/\" +\n")))) + #+end_src + + [ Note that if you are reading the Org source of this manual, you + need to use the command ~org-edit-special~ on the above code + blocks before copying the code. This is because Org automatically + prepends a comma to disambiguate those entries from actual + keywords of the current file. ] + +** Exclude certain files from file prompts +:PROPERTIES: +:CUSTOM_ID: h:53db09de-2cec-4670-b163-5cb791f997b4 +:END: + +#+vindex: denote-excluded-files-regexp +The user option ~denote-excluded-files-regexp~ is a regular expression +that matches files names which should be excluded from all Denote file +prompts. Such prompts are present when linking to a file with one of +the many commands, like ~denote-link~ ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]), or when trying +to open a file that may or may not exist ([[#h:ad91ca39-cf10-4e16-b224-fdf78f093883][Open an existing note or create it if missing]]). + +Functions that check for files include ~denote-directory-files~ and +~denote-file-prompt~. + +The match is performed with ~string-match-p~. + +[[#h:c916d8c5-540a-409f-b780-6ccbd90e088e][For developers or advanced users]]. + +** Exclude certain directories from all operations +:PROPERTIES: +:CUSTOM_ID: h:8458f716-f9c2-4888-824b-2bf01cc5850a +:END: + +#+vindex: denote-excluded-directories-regexp +The user option ~denote-excluded-directories-regexp~ instructs all +Denote functions that read or check file/directory names to omit +directories that match the given regular expression. The regexp needs +to match only the name of the directory, not its full path. + +Affected operations include file prompts and functions that return the +available files in the value of the user option ~denote-directory~ +([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]). + +File prompts are used by several commands, such as ~denote-link~ and +~denote-subdirectory~. + +Functions that check for files include ~denote-directory-files~ and +~denote-directory-subdirectories~. + +The match is performed with ~string-match-p~. + +[[#h:c916d8c5-540a-409f-b780-6ccbd90e088e][For developers or advanced users]]. + +** Exclude certain keywords from being inferred +:PROPERTIES: +:CUSTOM_ID: h:69e518ee-ed43-40ab-a5f4-c780a23e5358 +:END: + +#+vindex: denote-excluded-keywords-regexp +The user option ~denote-excluded-keywords-regexp~ omits keywords that +match a regular expression from the list of inferred keywords. + +Keywords are inferred from file names and provided at relevant prompts +as completion candidates when the user option ~denote-infer-keywords~ +is non-nil. + +The match is performed with ~string-match-p~. + +** Create a controlled vocabulary for keywords +:PROPERTIES: +:CUSTOM_ID: h:c0fb477f-4f99-4d76-9cce-132bcfcb351d +:END: + +Denote has two ways to know about keywords: the predefined list of +strings specified in the user option ~denote-known-keywords~ as well +as all the keywords it finds in the files of the ~denote-directory~ +when the user option ~denote-infer-keywords~ is set to a non-nil value +([[#h:69e518ee-ed43-40ab-a5f4-c780a23e5358][Exclude certain keywords from being inferred]]). + +While this is a viable setup, users may prefer to implement a +"controlled vocabulary". This is a predefined set of keywords whose +purpose is to avoid the creation of overly specific or inconsistent +keywords. + +To establish such a controlled vocabulary, users need only have +something like this in their configuration: + +#+begin_src emacs-lisp +;; Do not read keywords from files. The only source is the `denote-known-keywords'. +(setq denote-infer-keywords nil) + +;; Define the list of keywords. Each keyword is a string. +(setq denote-known-keywords (list "politics" "economics" "emacs" "philosophy")) +#+end_src + +** Use Denote commands from the menu bar or context menu +:PROPERTIES: +:CUSTOM_ID: h:c4290e15-e97e-4a9b-b8db-6b9738e37e78 +:END: + +Denote registers a submenu for the ~menu-bar-mode~. Users will find +the entry called "Denote". From there they can use their pointer to +select a command. For a sample of how this looks, read the +development log: . + +#+findex: denote-menu-bar-mode +The command ~denote-menu-bar-mode~ toggles the presentation of the +menu. It is enabled by default. + +Emacs also provides support for operations through a context menu. +This is typically the set of actions that are made available via a +right mouse click. Users who enable ~context-menu-mode~ can register +the Denote entry for it by adding the following to their configuration +file: + +#+begin_src emacs-lisp +(add-hook 'context-menu-functions #'denote-context-menu) +#+end_src + +* Renaming files +:PROPERTIES: +:CUSTOM_ID: h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca +:END: + +Denote provides commands to rename files and update their front matter +where relevant. For Denote to work, only the file name needs to be in +order, by following our naming conventions ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). +The linking mechanism, in particular, needs just the identifier in the +file name ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). + +We write front matter in notes for the user's convenience and for other +tools to make use of that information (e.g. Org's export mechanism). +The renaming mechanism takes care to keep this data in sync with the +file name, when the user performs a change. + +Renaming is useful for managing existing files created with Denote, +but also for converting older text files to Denote notes. Denote's +file-naming scheme is not specific to notes or text files: it is +relevant for all sorts of items, such as multimedia and PDFs that form +part of the user's longer-term storage. While Denote does not manage +such files (e.g. doesn't create links to them), it already has all the +mechanisms to facilitate the task of renaming them. + +#+vindex: denote-after-rename-file-hook +All renaming commands run the ~denote-after-rename-file-hook~ after a +succesful operation ([[#h:a947908e-1847-4471-ba07-377ee2f4b36c][Access the data of the latest note]]). They also +construct the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +Apart from renaming files, Denote can also rename only the buffer. +The idea is that the underlying file name is correct but it can be +easier to use shorter buffer names when displaying them on the mode +line or switching between then with commands like ~switch-to-buffer~. + +[[#h:3ca4db16-8f26-4d7d-b748-bac48ae32d69][Automatically rename Denote buffers]]. + +[[#h:9051f15d-ea7e-4b17-adc2-bc6a749c721b][Find duplicate identifiers and put them in a Dired buffer]]. + +** Rename a single file +:PROPERTIES: +:CUSTOM_ID: h:7cc9e000-806a-48da-945c-711bbc7426b0 +:END: + +#+findex: denote-rename-file +The ~denote-rename-file~ command renames a file and updates existing +front matter if appropriate. It is possible to do the same with +multiple files ([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files interactively]]). + +It always renames the file where it is located in the file system: +it never moves it to another directory. + +If in Dired, it considers =FILE= to be the one at point, else it +prompts with minibuffer completion for one. When called from Lisp, +=FILE= is a file system path represented as a string. + +If =FILE= has a Denote-compliant identifier, it retains it while +updating components of the file name referenced by the user option +~denote-prompts~ ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The ~denote-prompts~ option]]). By default, these are +the =TITLE= and =KEYWORDS=. The =SIGNATURE= is another one. When +called from Lisp, =TITLE= and =SIGNATURE= are strings, while +=KEYWORDS= is a list of strings. + +If there is no identifier, ~denote-rename-file~ creates an identifier +based on the following conditions: + +1. If the ~denote-prompts~ includes an entry for date prompts, then it + prompts for =DATE= and takes its input to produce a new identifier. For + use in Lisp, =DATE= must conform with ~denote-valid-date-p~. + +2. If =DATE= is nil (e.g. when ~denote-prompts~ does not include a + date entry), it uses the file attributes to determine the last + modified date of =FILE= and formats it as an identifier. + +3. As a fallback, it derives an identifier from the current date and + time. + +4. At any rate, if the resulting identifier is not unique among the + files in the variable ~denote-directory~, it increments it such + that it becomes unique. + +In interactive use, and assuming ~denote-prompts~ includes a title +entry, the ~denote-rename-file~ makes the =TITLE= prompt have +prefilled text in the minibuffer that consists of the current title of +=FILE=. The current title is either retrieved from the front matter +(such as the =#+title= in Org) or from the file name. + +The command does the same for the =SIGNATURE= prompt, subject to +~denote-prompts~, by prefilling the minibuffer with the current +signature of =FILE=, if any. + +Same principle for the =KEYWORDS= prompt: it converts the keywords in +the file name into a comma-separated string and prefills the minibuffer +with it (the =KEYWORDS= prompt accepts more than one keywords, each +separated by a comma, else the ~crm-separator~). + +For all prompts, the ~denote-rename-file~ interprets an empty input as +an instruction to remove that file name component. For example, if a +=TITLE= prompt is available and =FILE= is =20240211T093531--some-title__keyword1.org= +then it renames =FILE= to =20240211T093531__keyword1.org=. + +In interactive use, if there is no entry for a file name component in +~denote-prompts~, keep it as-is ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The ~denote-prompts~ option]]). + +When called from Lisp, the special symbol `keep-current' can be +used for the TITLE, KEYWORDS, SIGNATURE and DATE parameters to +keep them as-is. + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + ~vertico~ use the first available completion candidate instead. For + ~vertico~, the user must either move one up to select the prompt and + then type =RET= there with empty contents, or use the command + ~vertico-exit-input~ with empty contents. That Vertico command is + bound to =M-RET= as of this writing on 2024-02-13 08:08 +0200. ] + +When renaming =FILE=, the command reads its file type extension (like +=.org=) and preserves it through the renaming process. Files that have +no extension are left without one. + +As a final step, ask for confirmation, showing the difference +between old and new file names. Do not ask for confirmation if +the user option ~denote-rename-confirmations~ does not contain +the symbol ~modify-file-name~ ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-confirmations~ option]]). + +If =FILE= has front matter for =TITLE= and =KEYWORDS=, ask to rewrite +their values in order to reflect the new input, unless +~denote-rename-confirmations~ lacks ~rewrite-front-matter~. When the +~denote-save-buffers~ is nil (the default), do not save the underlying +buffer, thus giving the user the option to double-check the result, +such as by invoking the command ~diff-buffer-with-file~. The rewrite +of the =TITLE= and =KEYWORDS= in the front matter should not affect +the rest of the front matter. + +If the file does not have front matter but is among the supported file +types (per ~denote-file-type~), add front matter to the top of it and +leave the buffer unsaved for further inspection ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]). Save +the buffer if ~denote-save-buffers~ is non-nil ([[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffers~ option]]). + +Construct the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +Run the ~denote-after-rename-file-hook~ after renaming =FILE= ([[#h:a947908e-1847-4471-ba07-377ee2f4b36c][Access the data of the latest note]]). + +This command is intended to (i) rename Denote files, (ii) convert +existing supported file types to Denote notes, and (ii) rename +non-note files (e.g. =PDF=) that can benefit from Denote's file-naming +scheme. + +For a version of this command that works with multiple files +one-by-one, use ~denote-dired-rename-files~ ([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files interactively]]). + +*** The ~denote-rename-confirmations~ option +:PROPERTIES: +:CUSTOM_ID: h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7 +:END: + +#+vindex: denote-rename-confirmations +The user option ~denote-rename-confirmations~ controls what kind of +confirmation renaming commands ask for ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). Its value is +a list of symbols. + +The value is either nil, in which case no confirmation is ever +requested, or a list of symbols among the following: + +- ~modify-file-name~ means that renaming commands will ask for + confirmation before modifying the file name. + +- ~rewrite-front-matter~ means that renaming commands will ask for + confirmation before rewritting the front matter. + +- ~add-front-matter~ means that renaming commands will ask for + confirmation before adding new front matter to the file. + +The default behaviour of the ~denote-rename-file~ command (and others +like it) is to ask for an affirmative answer as a final step before +changing the file name and, where relevant, inserting or updating the +corresponding front matter. + +Specialized commands that build on top of ~denote-rename-file~ (or +related) may internally bind this user option to a non-nil value in +order to perform their operation (e.g. ~denote-dired-rename-files~ +goes through each marked Dired file, prompting for the information to +use, but carries out the renaming without asking for confirmation +([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files interactively]])). + +** Rename a single file based on its front matter +:PROPERTIES: +:CUSTOM_ID: h:3ab08ff4-81fa-4d24-99cb-79f97c13a373 +:END: + +#+findex: denote-rename-file-using-front-matter +In the previous section, we covered the more general mechanism of the +command ~denote-rename-file~ ([[#h:7cc9e000-806a-48da-945c-711bbc7426b0][Rename a single file]]). There is also a +way to have the same outcome by making Denote read the data in the +current file's front matter and use it to construct/update the file +name. The command for this is ~denote-rename-file-using-front-matter~. +It is only relevant for files that (i) are among the supported file +types, per ~denote-file-type~, and (ii) have the requisite front matter +in place. + +Suppose you have an =.org= file with this front matter ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]): + +#+begin_example +#+title: My sample note file +#+date: [2022-08-05 Fri 13:10] +#+filetags: :testing: +#+identifier: 20220805T131044 +#+end_example + +Its file name reflects this information: + +: 20220805T131044--my-sample-note-file__testing.org + +You want to change its title and keywords manually, so you modify it thus: + +#+begin_example +#+title: My modified sample note file +#+date: [2022-08-05 Fri 13:10] +#+filetags: :testing:denote:emacs: +#+identifier: 20220805T131044 +#+end_example + +At this stage, the file name still shows the old title and keywords. +You now invoke ~denote-rename-file-using-front-matter~ and it updates +the file name to: + +: 20220805T131044--my-modified-sample-note-file__testing_denote_emacs.org + +By default, the renaming is subject to a "yes or no" prompt that shows +the old and new names, just so the user is certain about the change. +Though this can be modified ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-confirmations~ option]]). + +The identifier of the file, if any, is never modified even if it is +edited in the front matter: Denote considers the file name to be the +source of truth in this case, to avoid potential breakage with typos and +the like. + +This command constructs the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +** Rename multiple files interactively +:PROPERTIES: +:CUSTOM_ID: h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde +:END: + +#+findex: denote-dired-rename-files +#+findex: denote-dired-rename-marked-files +The command ~denote-dired-rename-files~ (alias ~denote-dired-rename-marked-files~) +renames the files that are marked in a Dired buffer. Its behaviour is +similar to the ~denote-rename-file~ in that it prompts for a title, +keywords, and signature ([[#h:7cc9e000-806a-48da-945c-711bbc7426b0][Rename a single file]]). It does so over each +marked file, renaming one after the other. + +Unlike ~denote-rename-file~, the command ~denote-dired-rename-files~ +does not ask to confirm the changes made to the files: it performs +them outright (same as setting ~denote-rename-confirmations~ to a nil +value). This is done to make it easier to rename multiple files +without having to confirm each step. For an even more direct approach, +check the command ~denote-dired-rename-marked-files-with-keywords~. + +- [[#h:f365ff7e-2140-4e14-a92f-666ae97382a4][Rename by writing only keywords]] +- [[#h:ea5673cd-e6ca-4c42-a066-07dc6c9d57f8][Rename multiple files based on their front matter]] + +** Rename multiple files at once by asking only for keywords +:PROPERTIES: +:CUSTOM_ID: h:f365ff7e-2140-4e14-a92f-666ae97382a4 +:END: + +#+findex: denote-dired-rename-marked-files-with-keywords +The ~denote-dired-rename-marked-files-with-keywords~ command renames +marked files in Dired to conform with our file-naming scheme. It does +so by writing keywords to them. Specifically, it does the following: + +- retains the file's existing name and makes it the =TITLE= field, per + Denote's file-naming scheme; + +- sluggifies the =TITLE= and adjusts its letter casing, according to + our conventions; + +- prepends an identifier to the =TITLE=, if one is missing; + +- preserves the file's extension, if any; + +- prompts once for =KEYWORDS= and applies the user's input to the + corresponding field in the file name, rewriting any keywords that + may exist while removing keywords that do exist if =KEYWORDS= is + empty; + +- adds or rewrites existing front matter to the underlying file, if it + is recognized as a Denote note (per the ~denote-file-type~ user + option), such that it includes the new keywords. + +[ Note that the affected buffers are not saved, unless the user option + ~denote-rename-no-confirm~ is non-nil. Users can thus check them to + confirm that the new front matter does not cause any problems (e.g. + with the ~diff-buffer-with-file~ command). Multiple buffers can be + saved in one go with the command ~save-some-buffers~ (read its doc + string). ] + +Construct the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +Run the ~denote-after-rename-file-hook~ after the renaming is done. + +#+findex: denote-dired-rename-marked-files-add-keywords +#+findex: denote-dired-rename-marked-files-remove-keywords +For more specialized versions of this command that only add or remove +keywords, use ~denote-dired-rename-marked-files-add-keywords~ and +~denote-dired-rename-marked-files-remove-keywords~, respectively. + +** Rename multiple files based on their front matter +:PROPERTIES: +:CUSTOM_ID: h:ea5673cd-e6ca-4c42-a066-07dc6c9d57f8 +:END: + +#+findex: denote-dired-rename-marked-files-using-front-matter +As already noted, Denote can rename a file based on the data in its +front matter ([[#h:3ab08ff4-81fa-4d24-99cb-79f97c13a373][Rename a single file based on its front matter]]). The +command ~denote-dired-rename-marked-files-using-front-matter~ extends +this principle to a batch operation which applies to all marked files in +Dired. + +Marked files must count as notes for the purposes of Denote, which +means that they at least have an identifier in their file name and use +a supported file type, per ~denote-file-type~. Files that do not meet +this criterion are ignored, because Denote cannot know if they have +front matter and what that may be. For such files, it is still +possible to rename them interactively ([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files interactively]]). + +** Rename a file by changing only its file type +:PROPERTIES: +:CUSTOM_ID: h:85b65995-89fd-4978-bba3-7bb6c8d6f945 +:END: + +#+findex: denote-change-file-type-and-front-matter +The command ~denote-change-file-type-and-front-matter~ provides the +convenience of converting a note taken in one file type, say, =.txt= +into another like =.org=. It presents a choice among the +~denote-file-type~ options. + +The conversion does NOT modify the existing front matter. Instead, it +prepends new front matter to the top of the file. We do this as a +safety precaution since the user can, in principle, add arbitrary +extras to their front matter that we would not want to touch. + +If in Dired, ~denote-change-file-type-and-front-matter~ operates on the +file at point, else the current file, else it prompts with minibuffer +completion for one. + +The title of the file is retrieved from a line starting with a title +field in the file's front matter, depending on the previous file type +(e.g. =#+title= for Org). The same process applies for keywords. + +As a final step, the command asks for confirmation, showing the +difference between old and new file names. + +This command constructs the file name in accordance with the user option +~denote-file-name-components-order~ ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +** Rename a file by adding or removing a title interactively +:PROPERTIES: +:CUSTOM_ID: h:a26e28c7-8222-4377-92e9-3b0a709010a5 +:END: + +#+findex: denote-rename-file-title +The command ~denote-rename-file-title~ streamlines the process of +interactively adding or removing a title to/from a file, while +changing its file name accordingly. It asks for a title using the +familiar minibuffer prompt ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). It then renames +the file. The command respect the values of +~denote-rename-confirmations~ and ~denote-save-buffers~: + +- [[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-confirmations~ option]]. +- [[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffers~ option]]. + +Technically, ~denote-rename-file-title~ is a wrapper for +~denote-rename-file~, doing all the things that does ([[#h:7cc9e000-806a-48da-945c-711bbc7426b0][Rename a single file]]). + +Concretely, this command can add or remove a title in one go. It +does it by prepopulating the minibuffer prompt with the existing +title. Users can then modify it. An empty input means to remove +the title altogether ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + ~vertico~ use the first available completion candidate instead. For + ~vertico~, the user must either move one up to select the prompt and + then type =RET= there with empty contents, or use the command + ~vertico-exit-input~ with empty contents. That Vertico command is + bound to =M-RET= as of this writing on 2024-06-30 10:37 +0300. ] + +** Rename a file by adding or removing keywords interactively +:PROPERTIES: +:CUSTOM_ID: h:ad4dde4a-8e88-470a-97ae-e7b9d4b41fb4 +:END: + +#+findex: denote-rename-file-keywords +The command ~denote-rename-file-keywords~ streamlines the process of +interactively adding or removing keywords to a file, while changing +its file name and front matter accordingly. It asks for keywords using +the familiar minibuffer prompt ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). It then +renames the file ([[#h:3ab08ff4-81fa-4d24-99cb-79f97c13a373][Rename a single file based on its front matter]]). +The command respect the values of ~denote-rename-confirmations~ and +~denote-save-buffers~: + +- [[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-confirmations~ option]]. +- [[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffers~ option]]. + +Technically, ~denote-rename-file-keywords~ is a wrapper for +~denote-rename-file~, doing all the things that does ([[#h:7cc9e000-806a-48da-945c-711bbc7426b0][Rename a single file]]). + +Concretely, this command can add or remove keywords in one go. It does +it by prepopulating the minibuffer prompt with the existing keywords. +Users can then use the ~crm-separator~ (normally a comma), to write +new keywords or edit what is in the prompt to rewrite them +accordingly. An empty input means to remove all keywords ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + ~vertico~ use the first available completion candidate instead. For + ~vertico~, the user must either move one up to select the prompt and + then type =RET= there with empty contents, or use the command + ~vertico-exit-input~ with empty contents. That Vertico command is + bound to =M-RET= as of this writing on 2024-06-30 10:37 +0300. ] + +** Rename a file by adding or removing a signature interactively +:PROPERTIES: +:CUSTOM_ID: h:b08a350f-b269-47ed-8c2a-b8ecf1b63c7f +:END: + +#+findex: denote-rename-file-signature +The command ~denote-rename-file-signature~ streamlines the process of +interactively adding or removing a signature to/from a file, while +changing its file name accordingly. It asks for a signature using the +familiar minibuffer prompt ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). It then renames +the file. The command respect the values of +~denote-rename-confirmations~ and ~denote-save-buffers~: + +- [[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-confirmations~ option]]. +- [[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffers~ option]]. + +Technically, ~denote-rename-file-signature~ is a wrapper for +~denote-rename-file~, doing all the things that does ([[#h:7cc9e000-806a-48da-945c-711bbc7426b0][Rename a single file]]). + +Concretely, this command can add or remove a signature in one go. It +does it by prepopulating the minibuffer prompt with the existing +signature. Users can then modify it. An empty input means to remove +the signature altogether ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the empty + minibuffer contents as they are, though popular packages like + ~vertico~ use the first available completion candidate instead. For + ~vertico~, the user must either move one up to select the prompt and + then type =RET= there with empty contents, or use the command + ~vertico-exit-input~ with empty contents. That Vertico command is + bound to =M-RET= as of this writing on 2024-06-30 10:37 +0300. ] + +** Find duplicate identifiers and put them in a Dired buffer +:PROPERTIES: +:CUSTOM_ID: h:9051f15d-ea7e-4b17-adc2-bc6a749c721b +:END: + +Denote takes care to create unique identifiers, though its mechanism +relies on reading the existing identifiers in the ~denote-directory~ +or the current directory. When we are renaming files across different +directories, there is a small chance that some files have the same +attributes and are thus assigned identical identifiers. If those files +ever make it into a consolidated ~denote-directory~, we will have +duplicates, which break the linking mechanism. + +As this is an edge case, we do not include any code to address it in +the Denote code base. Though here is a way to find duplicate +identifiers inside the current directory: + +#+begin_src emacs-lisp +(defun my-denote--get-files-in-dir (directory) + "Return file names in DIRECTORY." + (directory-files directory :full-paths directory-files-no-dot-files-regexp)) + +(defun my-denote--same-identifier-p (file1 file2) + "Return non-nil if FILE1 and FILE2 have the same identifier." + (let ((id1 (denote-retrieve-filename-identifier file1)) + (id2 (denote-retrieve-filename-identifier file2))) + (equal id1 id2))) + +(defun my-denote-find-duplicate-identifiers (directory) + "Find all files in DIRECTORY that need a new identifier." + (let* ((ids (my-denote--get-files-in-dir directory)) + (unique-ids (seq-uniq ids #'my-denote--same-identifier-p))) + (seq-difference ids unique-ids #'equal))) + +(defun my-denote-dired-show-duplicate-identifiers (directory) + "Put duplicate identifiers from DIRECTORY in a dedicated Dired buffer." + (interactive + (list + (read-directory-name "Select DIRECTORY to check for duplicate identifiers: " default-directory))) + (if-let* ((duplicates (my-denote-find-duplicate-identifiers directory))) + (dired (cons (format "Denote duplicate identifiers" directory) duplicates)) + (message "No duplicates identifiers in `%s'" directory))) +#+end_src + +Evaluate this code and then call the command ~my-denote-dired-show-duplicate-identifiers~. +If there are duplicates, it will put them in a dedicated Dired buffer. +From there, you can view the file contents as usual, and manually edit +the identifiers as you see fit (e.g. edit them one by one, or change +to the writable Dired and record a keyboard macro that makes use of a +counter to increment by 1---contact me if you need any help). + +** Faces used by rename commands +:PROPERTIES: +:CUSTOM_ID: h:ab3f355a-f763-43ae-a4c9-179d2d9265a5 +:END: + +These are the faces used by the various Denote rename commands to +style or highlight the old/new/current file shown in the relevant +minibuffer prompts: + +- ~denote-faces-prompt-current-name~ +- ~denote-faces-prompt-new-name~ +- ~denote-faces-prompt-old-name~ + +* The file-naming scheme +:PROPERTIES: +:CUSTOM_ID: h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d +:END: + +#+vindex: denote-directory +Notes are stored in the ~denote-directory~. The default path is +=~/Documents/notes=. The ~denote-directory~ can be a flat listing, +meaning that it has no subdirectories, or it can be a directory tree. +Either way, Denote takes care to only consider "notes" as valid +candidates in the relevant operations and will omit other files or +directories. + +Every note produced by Denote follows this pattern by default +([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]): + +: DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION + +The =DATE= field represents the date in year-month-day format followed +by the capital letter =T= (for "time") and the current time in +hour-minute-second notation. The presentation is compact: +=20220531T091625=. The =DATE= serves as the unique identifier of each +note and, as such, is also known as the file's ID or identifier. + +File names can include an arbitrary string of alphanumeric characters +in the =SIGNATURE= field. Signatures have no clearly defined purpose +and are up to the user to define. They can serve as special labels, +such as =part1= and =part2= of a large file, or as priority indicators +like =a=, =b=, =c=, or even context/scope specifiers like =home= and +=work=. Another use-case is to write sequences of thoughts, such that +notes form a hierarchy, something we support with the optional and +comprehensive extension =denote-sequence.el= ([[#h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f][Write sequence notes or "folgezettel"]]). +Signatures are an optional extension to Denote's file-naming scheme. +In the simplest form, they can be added to newly created files on +demand, with the command ~denote-signature~, or by modifying the value +of the user option ~denote-prompts~ ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The ~denote-prompts~ option]]). + +The =TITLE= field is the title of the note, as provided by the user. +It automatically gets downcased by default and is also hyphenated +([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). An entry about "Economics +in the Euro Area" produces an =economics-in-the-euro-area= string for +the =TITLE= of the file name. + +The =KEYWORDS= field consists of one or more entries demarcated by an +underscore (the separator is inserted automatically). Each keyword is +a string provided by the user at the relevant prompt which broadly +describes the contents of the entry. + +Each of the keywords is a single word, with multiple keywords providing +the multi-dimensionality needed for advanced searches through Denote +files. Users who need to compose a keyword out of multiple words such +as camelCase/CamelCase and are encouraged to use the +~denote-file-name-slug-functions~ user option accordingly +([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). + +#+vindex: denote-file-type +The =EXTENSION= is the file type. By default, it is =.org= (~org-mode~) +though the user option ~denote-file-type~ provides support for Markdown +with YAML or TOML variants (=.md= which runs ~markdown-mode~) and plain +text (=.txt= via ~text-mode~). Consult its doc string for the minutiae. +While files end in the =.org= extension by default, the Denote code base +does not actually depend on org.el and/or its accoutrements. + +Examples: + +: 20220610T043241--initial-thoughts-on-the-zettelkasten-method__notetaking.org +: 20220610T062201--define-custom-org-hyperlink-type__denote_emacs_package.md +: 20220610T162327--on-hierarchy-and-taxis__notetaking_philosophy.txt + +The different field separators, namely =--= and =__= introduce an +efficient way to anchor searches (such as with Emacs commands like +~isearch~ or from the command-line with ~find~ and related). A query +for =_word= always matches a keyword, while a regexp in the form of, +say, ="\\([0-9T]+?\\)--\\(.*?\\)_"= captures the date in group =\1= and +the title in =\2= (test any regular expression in the current buffer by +invoking =M-x re-builder=). + +[[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]. + +The ~denote-prompts~ can be configured in such ways to yield the +following file name permutations: + +: DATE.EXT +: DATE--TITLE.EXT +: DATE__KEYWORDS.EXT +: DATE==SIGNATURE.EXT +: DATE==SIGNATURE--TITLE.EXT +: DATE==SIGNATURE--TITLE__KEYWORDS.EXT +: DATE==SIGNATURE__KEYWORDS.EXT + +When in doubt, stick to the default design, which is carefully +considered and works well ([[#h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd][Change the order of file name components]]). + +While Denote is an Emacs package, notes should work long-term and not +depend on the functionality of a specific program. The file-naming +scheme we apply guarantees that a listing is readable in a variety of +contexts. The Denote file-naming scheme is, in essence, an effective, +low-tech invention. + +** Change the order of file name components +:PROPERTIES: +:CUSTOM_ID: h:dc8c40e0-233a-4991-9ad3-2cf5f05ef1cd +:END: + +#+vindex: denote-file-name-components-order +Our standard file-naming scheme prescribes a specific order for the +file name components ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). Though we provide the +user option ~denote-file-name-components-order~ to let the user +reorder them as they see fit. + +The value of this user option is a list of the following symbols: + +- ~identifier~: This is the combination of the date and time. When it + is the first on the list, it looks like =20240519T073456= and does + not have a component separator of its own due its unambiguous + format. When it is placed anywhere else in the file name, it is + prefixed with =@@=, so it looks like =@@20240519T073456=. + +- ~signature~: This is an arbitrary string that can be used to qualify + the file in some way, according to the user's methodology (e.g. to + add a sequence to notes). The string is always prefixed with the + ~==~ to remain unambiguous. + +- ~title~: This is an arbitrary string which describes the file. It is + always prefixed with =--= to be unambiguous. + +- ~keywords~: This is a series of one or more words that succinctly + group the file. Multiple keywords are separated by an underscore + prefixed to each of them. The file name component is always prefixed + with =__=. + +All four symbols must appear exactly once. Duplicates are ignored. Any +missing symbol is added automatically. + +Some examples: + +#+begin_src emacs-lisp +(setq denote-file-name-components-order '(identifier signature title keywords)) +;; => 20240519T07345==hello--this-is-the-title__denote_testing.org + +(setq denote-file-name-components-order '(signature identifier title keywords)) +;; => ==hello@@20240519T07345--this-is-the-title__denote_testing.org + +(setq denote-file-name-components-order '(title signature identifier keywords)) +;; => --this-is-the-title==hello@@20240519T07345__denote_testing.org + +(setq denote-file-name-components-order '(keywords title signature identifier)) +;; => __denote_testing--this-is-the-title==hello@@20240519T07345.org +#+end_src + +Also see how to configure the Denote prompts, which affect which +components are actually used in the order specified herein ([[#h:f9204f1f-fcee-49b1-8081-16a08a338099][The ~denote-prompts~ option]]). + +Before deciding on this, please consider the longer-term implications +of file names with varying patterns. Consistency makes things +predictable and thus easier to find. So pick one order and never touch +it again. When in doubt, leave the default file-naming scheme as-is. + +** Sluggification of file name components +:PROPERTIES: +:CUSTOM_ID: h:ae8b19a1-7f67-4258-96b3-370a72c43f4e +:END: + +Files names can contain any character that the file system +permits. Denote imposes a few additional restrictions: + ++ The tokens "==", =__= and =--= are interpreted by Denote and should + appear only once. + ++ The dot character is not allowed in a note's file name, except to + indicate the file type extension. Denote recognises two extensions + for encrypted files, like =.txt.gpg=. + +By default, Denote enforces other rules to file names through the user +option ~denote-file-name-slug-functions~. These rules are applied to +file names by default: + ++ What we count as "illegal characters" are removed. + ++ Input for a file title is hyphenated. The original value is + preserved in the note's contents ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]). + ++ Spaces or other delimiters are removed from keywords, meaning that + =hello-world= becomes =helloworld=. This is because hyphens in + keywords do not work everywhere, such as in Org. Plus, hyphens are + word separators in the title and we want to keep distinct separators + for each component to make search easier and semantic + ([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). + ++ Signatures are like the above, but use the equals sign instead of + hyphens as a word separator. + ++ All file name components are downcased. Further down we document how + to deviate from these rules, such as to accept input of the form + =helloWorld= or =HelloWorld= verbatim. + +Denote imposes these restrictions to enforce uniformity, which is +helpful long-term as it keeps all files with the same predictable +pattern. Too many permutations make searches more difficult to express +accurately and be confident that the matches cover all files. +Nevertheless, one of the principles of Denote is its flexibility or +hackability and so users can deviate from the aforementioned +([[#h:d375c6d2-92c7-425f-9d9d-219ff47ed2a3][User-defined sluggification of file name components]]). + +** User-defined sluggification of file name components +:PROPERTIES: +:CUSTOM_ID: h:d375c6d2-92c7-425f-9d9d-219ff47ed2a3 +:END: + +#+vindex: denote-file-name-slug-functions +The user option ~denote-file-name-slug-functions~ controls the +sluggification of file name components ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). +The default method is outlined above and in the previous section +([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +The value of this user option is an alist where each element is a cons +cell of the form =(COMPONENT . METHOD)=. For example, here is the +default value: + +#+begin_example emacs-lisp +'((title . denote-sluggify-title) + (signature . denote-sluggify-signature) + (keyword . denote-sluggify-keyword)) +#+end_example + +- The =COMPONENT= is an unquoted symbol among =title=, =signature=, + =keyword=, which refers to the corresponding component of the file + name. + +- The =METHOD= is a function to format the given component. This + function must take a string as its parameter and return the string + formatted for the file name. Note that even in the case of the + =keyword= component, the function receives one string representing a + single keyword and returns it formatted for the file name. Joining + the keywords together is handled internally by Denote. + +One commonly requested deviation from the sluggification rules is to +not sluggify individual keywords, such that the user's input is taken +as-is. This can be done as follows: + +#+begin_src emacs-lisp +(setq denote-file-name-slug-functions + '((title . denote-sluggify-title) + (keyword . identity) + (signature . denote-sluggify-signature))) +#+end_src + +The ~identity~ function simply returns the string it receives, thus +not altering it in any way. + +Another approach is to keep the sluggification but not downcase the +string. We can do this by modifying the original functions used by +Denote. For example, we have this: + +#+begin_src emacs-lisp +;; The original function for reference +(defun denote-sluggify-title (str) + "Make STR an appropriate slug for title." + (downcase + (denote-slug-hyphenate + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" str)))) + +;; Our variant of the above, which does the same thing except from +;; downcasing the string. +(defun my-denote-sluggify-title (str) + "Make STR an appropriate slug for title." + (denote-slug-hyphenate + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" str))) + +;; Now we use our function to sluggify titles without affecting their +;; letter casing. +(setq denote-file-name-slug-functions + '((title . my-denote-sluggify-title) ; our function here + (signature . denote-sluggify-signature) + (keyword . denote-sluggify-keyword))) +#+end_src + +Follow this principle for all the sluggification functions ([[#h:d1e4eb5b-e7f2-4a3b-9243-e1c653817a4a][Custom sluggification to remove non-ASCII characters]]). + +To access the source code, use either of the following built-in +methods: + +1. Call the command ~find-library~ and search for ~denote~. Then + navigate to the symbol you are searching for. + +2. Invoke the command ~describe-symbol~, search for the symbol you are + interested in, and from the resulting Help buffer either click on + the first link or do =M-x help-view-source= (bound to =s= in Help + buffers, by default). + +Remember that deviating from the default file-naming scheme of Denote +will make things harder to use in the future, as files can/will have +permutations that create uncertainty. The sluggification scheme and +concomitant restrictions we impose by default are there for a very +good reason: they are the distillation of years of experience. Here we +give you what you wish, but bear in mind it may not be what you need. +You have been warned. + +*** Custom sluggification to remove non-ASCII characters +:PROPERTIES: +:CUSTOM_ID: h:d1e4eb5b-e7f2-4a3b-9243-e1c653817a4a +:END: + +A common use-case for Denote is to rename files such as videos +downloaded from the Internet. Sometimes, those files have Unicode +characters that (i) not all fonts support and (ii) create all sorts of +problems with pattern matching, such as when searching through file +names. + +By default, Denote does not remove Unicode characters because users +may actually want them (e.g. Latin characters with accents). Those who +do, however, wish to keep everything limited to the ASCII range can +use the following in their Emacs configuration ([[#h:d375c6d2-92c7-425f-9d9d-219ff47ed2a3][User-defined sluggification of file name components]]). + +#+begin_src emacs-lisp +;; These are the same as the default Denote sluggification functions, +;; except they remove all non-ASCII characters. +(defun my-denote-sluggify-title (str) + (downcase + (denote-slug-hyphenate + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" + (denote-slug-keep-only-ascii str))))) + +(defun my-denote-sluggify-signature (str) + (downcase + (denote-slug-put-equals + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/-]*" "" + (denote-slug-keep-only-ascii str))))) + +(defun my-denote-sluggify-keyword (str) + (downcase + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/_ =-]*" "" + (denote-slug-keep-only-ascii str)))) + +(defcustom denote-file-name-slug-functions + '((title . my-denote-sluggify-title) + (signature . my-denote-sluggify-signature) + (keyword . my-denote-sluggify-keyword))) +#+end_src + +** Features of the file-naming scheme for searching or filtering +:PROPERTIES: +:CUSTOM_ID: h:1a953736-86c2-420b-b566-fb22c97df197 +:END: + +By default, file names have three fields and two sets of field +delimiters between them: + +: DATE--TITLE__KEYWORDS.EXTENSION + +When a signature is present, this becomes: + +: DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION + +Field delimiters practically serve as anchors for easier searching. +Consider this example: + +: 20220621T062327==1a2--introduction-to-denote__denote_emacs.txt + +You will notice that there are two matches for the word =denote=: one +in the title field and another in the keywords' field. Because of the +distinct field delimiters, if we search for =-denote= we only match +the first instance while =_denote= targets the second one. When +sorting through your notes, this kind of specificity is +invaluable---and you get it for free from the file names alone! +Similarly, a search for ==1= will show all notes that are related to +each other by virtue of their signature. + +Users can get a lot of value out of this simple yet effective +arrangement, even if they have no knowledge of regular expressions. +One thing to consider, for maximum effect, is to avoid using +multi-word keywords as those can get hyphenated like the title and +will thus interfere with the above: either set the user option +~denote-allow-multi-word-keywords~ to nil or simply insert single +words at the relevant prompts. + +* Front matter +:PROPERTIES: +:CUSTOM_ID: h:13218826-56a5-482a-9b91-5b6de4f14261 +:END: + +Notes have their own "front matter". This is a block of data at the top +of the file, with no empty lines between the entries, which is +automatically generated at the creation of a new note. The front matter +includes the title and keywords (aka "tags" or "filetags", depending on +the file type) which the user specified at the relevant prompt, as well +as the date and unique identifier, which are derived automatically. + +This is how it looks for Org mode (when ~denote-file-type~ is nil or the +=org= symbol): + +#+begin_example +#+title: This is a sample note +#+date: [2022-06-30 Thu 16:09] +#+filetags: :denote:testing: +#+identifier: 20220630T160934 +#+end_example + +For Markdown with YAML (~denote-file-type~ has the =markdown-yaml= +value), the front matter looks like this: + +#+begin_example +--- +title: "This is a sample note" +date: 2022-06-30T16:09:58+03:00 +tags: ["denote", "testing"] +identifier: "20220630T160958" +--- +#+end_example + +For Markdown with TOML (~denote-file-type~ has the =markdown-toml= +value), it is: + +#+begin_example ++++ +title = "This is a sample note" +date = 2022-06-30T16:10:13+03:00 +tags = ["denote", "testing"] +identifier = "20220630T161013" ++++ +#+end_example + +And for plain text (~denote-file-type~ has the =text= value), we have +the following: + +#+begin_example +title: This is a sample note +date: 2022-06-30 +tags: denote testing +identifier: 20220630T161028 +--------------------------- +#+end_example + +#+vindex: denote-date-format +The format of the date in the front matter is controlled by the user +option ~denote-date-format~. When nil, Denote uses a file-type-specific +format: + +- For Org, an inactive timestamp is used, such as + =[2022-06-30 Wed 15:31]=. + +- For Markdown, the RFC3339 standard is applied: + =2022-06-30T15:48:00+03:00=. + +- For plain text, the format is that of ISO 8601: =2022-06-30=. + +If the value is a string, ignore the above and use it instead. The +string must include format specifiers for the date. These are described +in the doc string of ~format-time-string~.. + +** Change the front matter format +:PROPERTIES: +:CUSTOM_ID: h:7f918854-5ed4-4139-821f-8ee9ba06ad15 +:END: + +Per Denote's design principles, the code is hackable. All front matter +is stored in variables that are intended for public use. We do not +declare those as "user options" because (i) they expect the user to have +some degree of knowledge in Emacs Lisp and (ii) implement custom code. + +[ NOTE for tinkerers: code intended for internal use includes double + hyphens in its symbol. "Internal use" means that it can be changed + without warning and with no further reference in the change log. Do + not use any of it without understanding the consequences. ] + +The variables which hold the front matter format are: + +#+vindex: denote-org-front-matter +- ~denote-org-front-matter~ + +#+vindex: denote-text-front-matter +- ~denote-text-front-matter~ + +#+vindex: denote-toml-front-matter +- ~denote-toml-front-matter~ + +#+vindex: denote-yaml-front-matter +- ~denote-yaml-front-matter~ + +These variables have a string value with specifiers that are used by the +~format~ function. The formatting operation passes four arguments which +include the values of the given entries. If you are an advanced user +who wants to edit this variable to affect how front matter is produced, +consider using something like =%2$s= to control where the Nth argument +is placed. + +When editing the value, make sure to: + +1. Not use empty lines inside the front matter block. + +2. Insert at least one empty line after the front matter block and do + not use any empty line before it. + +These help with consistency and might prove useful if we ever need to +operate on the front matter as a whole. + +With those granted, below are some examples. The approach is the same +for all variables. + +#+begin_src emacs-lisp +;; Like the default, but upcase the entries +(setq denote-org-front-matter + "#+TITLE: %s +#+DATE: %s +#+FILETAGS: %s +#+IDENTIFIER: %s +\n") + +;; Change the order (notice the %N$s notation) +(setq denote-org-front-matter + "#+title: %1$s +#+filetags: %3$s +#+date: %2$s +#+identifier: %4$s +\n") + +;; Remove the date +(setq denote-org-front-matter + "#+title: %1$s +#+filetags: %3$s +#+identifier: %4$s +\n") + +;; Remove the date and the identifier +(setq denote-org-front-matter + "#+title: %1$s +#+filetags: %3$s +\n") +#+end_src + +Note that ~setq~ has a global effect: it affects the creation of all new +notes. Depending on the workflow, it may be preferrable to have a +custom command which ~let~ binds the different format. We shall not +provide examples at this point as this is a more advanced feature and we +are not yet sure what the user's needs are. Please provide feedback and +we shall act accordingly. + +** Regenerate front matter +:PROPERTIES: +:CUSTOM_ID: h:54b48277-e0e5-4188-ad54-ef3db3b7e772 +:END: + +As part of version 4.0.0, the command ~denote-add-front-matter~ is +superseded by ~denote-rename-file~ and related ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). Those +commands will add missing front matter or rewrite the modified lines +of existing front matter. + +* Linking notes +:PROPERTIES: +:CUSTOM_ID: h:fc913d54-26c8-4c41-be86-999839e8ad31 +:END: + +Denote offers several commands for linking between notes. Those use +the =denote:= hyperlink type. There are two types of links supported +by Denote: + +- Direct links :: A direct link points to a file inside the + ~denote-directory~. The link is constructed by using the =denote:= + prefix and the target file's identifier ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + This looks like =denote:20250328T075526=. The syntax of a link + depends on the file type. For example, in Org and plain text links look like + =[[denote:20250328T075526][The title of the target file]]=, while in + Markdown they are written as =[The title of the target file](denote:20250328T075526)=. + +- Query links :: The =denote:= hyperlink type also supports special + qualifiers that change how the target of the link is interpreted. + The qualifier is a special token than tells Denote how to treat the + target of the link. It is written thus =denote:TOKEN:QUERY=. There + are two kinds of tokens: =query-contents= and =query-filenames=. + Those determine how the query terms are used. As their names + suggest, these two tokens trigger a search in (i) the file contents + of all readable files or (ii) in the file names only. They are, in + other words, counterparts of the Unix ~grep~ and ~find~ programs, + respectively. + +The following sections cover all the details ([[#h:4f354db1-aa78-47fd-ac60-c3d1e0f0b0a4][Why are some Org links opening outside Emacs?]]). + +** Add a single direct link using a file name prompt +:PROPERTIES: +:CUSTOM_ID: h:5e5e3370-12ab-454f-ba09-88ff44214324 +:END: + +#+findex: denote-link +#+findex: denote-insert-link +The ~denote-link~ command (alias ~denote-insert-link~) inserts a link +at point to a file selected at the minibuffer prompt. Links are +formatted depending on the file type of the current note. In Org and +plain text buffers, links are formatted thus: =[[denote:IDENTIFIER][DESCRIPTION]]=. +While in Markdown they are expressed as =[DESCRIPTION](denote:IDENTIFIER)=. + +When ~denote-link~ is called with a prefix argument (=C-u= by +default), it formats links like =[[denote:IDENTIFIER]]=, regardless of +file type ([[#h:156c5ea3-147b-4f9d-a404-86a00558c60a][Fontify links in non-Org buffers]]). The user might prefer +its simplicity. + +By default, the description of the link is determined thus: + +- If the region is active, its text becomes the description of the + link. In other words, the region text becomes the link. +- If the region is active but has no text, the description is empty + and so the link is formatted the same way as if using the =C-u= + prefix argument. +- If there is no region active, the description consists of the target + file's signature and title, using the former only if it is present. + The title is retrieved either from the front matter or the file + name. +- If the target file has no signature, the title is used. + +To insert multiple such links at once, use the command +~denote-add-links~ ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp in their file name]]). + +If you want to directly link to a single file whose contents match a +given query, then use the command ~denote-link-to-file-with-contents~ +([[#h:25a983ca-049e-43d4-8f6e-06a2325e2c3c][Adding a direct link to a file whose contents include the given query]]). + +#+vindex: denote-faces-link +Links are styled with the ~denote-faces-link~ face, which looks +exactly like an ordinary link by default. + +[ We optionally support direct links to a file followed by an extra + target to an Org headings ([[#h:d99de1fb-b1b7-4a74-8667-575636a4d6a4][The ~denote-org-store-link-to-heading~ user option]]). + Other file types do not have the features of Org, so we cannot + generalise this. ] + +** Add a direct link to a file whose contents include the given query +:PROPERTIES: +:CUSTOM_ID: h:25a983ca-049e-43d4-8f6e-06a2325e2c3c +:END: + +#+findex: denote-link-to-file-with-contents +The ~denote-link~ command that we covered before prompts to select a +file among those in the ~denote-directory~ ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Adding a single direct link using a file name prompt]]). +The match is done against the file's name. Users may, however, be +interested to create a link to a file whose contents include some +text, regardless of how the file name is called. To this end, the +command ~denote-link-to-file-with-contents~, (i) prompts for a query +which is a plain string or regular expression, (ii) if there are +matching files, asks to select one among them, and (iii) inserts the +direct link at point. + +When called with an optional prefix argument (=C-u= by default), the +command ~denote-link-to-file-with-contents~ creates a link that does +not include a description for the target file: it just has the file's +identifier (same as with ~denote-link~). + +The command ~denote-link-to-file-with-contents~ is the counterpart of +~denote-link-to-all-files-with-contents~ ([[#h:299d3aeb-9946-489e-bd91-e06f8c4ae2a9][Insert links to all files matching a query in their contents]]). + +** Add a query link +:PROPERTIES: +:CUSTOM_ID: h:d9a84289-2f73-4ef9-b4f0-9a0aa3e9bf0d +:END: + +As noted in the introduction to this section of the manual, the +=denote:= hyperlink type supports query links ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). Unlike +direct links, they do not point to any given file. Instead, they +trigger a search, whose results are displayed in a separate buffer. + +Query links are expressed as =denote:TOKEN:QUERY=, where =TOKEN= is +either =query-contents= or =query-filenames=, while =QUERY= is a +string or Emacs regular expression to search for. + +The exact syntax of a query link depends on the file type. In Org and +plain text buffers, links are of the form =[[denote:TOKEN:QUERY][QUERY]]=. +In Markdown, they are formatted as =[QUERY](denote:TOKEN:QUERY)=. In +all cases, the description of the link is the query text itself. + +#+findex: denote-query-contents-link +The command ~denote-query-contents-link~ inserts a link at point that +triggers a search in the file contents of all readable documents in +the ~denote-directory~ ([[#h:435592bc-e896-429f-a599-9f1bcd5ab9b8][Interact with the links buffer]]). This is the +equivalent of the Unix ~grep~ command and uses the built-in Emacs Xref +interface ([[#h:893eec49-d7be-4603-bcff-fcc247244011][Speed up backlinks' or query links' buffer creation?]]). +Matches are displayed in a separate buffer, highlighting the exact +text while showing its context. + +#+findex: denote-query-filenames-link +The command ~denote-query-filenames-link~ creates a link at point that +initiates a search across file names in the ~denote-directory~. This +is the equivalent of the Unix ~find~ command. Results are placed in a +Dired buffer ([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Display filtered and sorted files with ~denote-sort-dired~]]). + +#+vindex: denote-query-links-display-buffer-action +The user option ~denote-query-links-display-buffer-action~ controls +the placement of query link buffers. By default, they are designed to +appear below the current window. + +#+vindex: denote-faces-query-link +Query links are styled with the ~denote-faces-query-link~ face, which +looks a bit different that ~denote-faces-link~ (though this depends on +the active theme). + +** Insert links to all files matching a query in their file name +:PROPERTIES: +:CUSTOM_ID: h:9bec2c83-36ca-4951-aefc-7187c5463f90 +:END: + +#+findex: denote-add-links +The command ~denote-add-links~ adds links at point to all file names +in the ~denote-directory~ that match a regular expression or plain +string. This is similar to the ~denote-link~ command, which +establishes a direct link to a specified file ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Adding a single direct link]]). +Links to files whose names match the given search terms are inserted +as a typographic list, such as: + +#+begin_example +- link1 +- link2 +- link3 +#+end_example + +Each link is formatted according to the file type of the current note, +as explained further above about the ~denote-link~ command. The current +note is excluded from the matching entries (adding a link to itself is +pointless). + +When called with a prefix argument (=C-u=) ~denote-add-links~ will +format all links as =[[denote:IDENTIFIER]]=, hence a typographic list: + +#+begin_example +- [[denote:IDENTIFIER-1]] +- [[denote:IDENTIFIER-2]] +- [[denote:IDENTIFIER-3]] +#+end_example + +Same examples of a regular expression that can be used with this +command: + +- =journal= match all files which include =journal= anywhere in their + name. + +- =_journal= match all files which include =journal= as a keyword. + +- =^2022.*_journal= match all file names starting with =2022= and + including the keyword =journal=. + +- =\.txt= match all files including =.txt=. In practical terms, this + only applies to the file extension, as Denote automatically removes + dots (and other characters) from the base file name. + +If files are created with ~denote-sort-keywords~ as non-nil (the +default), then it is easy to write a regexp that includes multiple +keywords in alphabetic order: + +- =_denote.*_package= match all files that include both the =denote= and + =package= keywords, in this order. + +- =\(.*denote.*package.*\)\|\(.*package.*denote.*\)= is the same as + above, but out-of-order. + +Remember that regexp constructs only need to be escaped once (like =\|=) +when done interactively but twice when called from Lisp. What we show +above is for interactive usage. + +Links are created only for files which qualify as a "note" for our +purposes ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). + +** Insert links to all files matching a query in their contents +:PROPERTIES: +:CUSTOM_ID: h:299d3aeb-9946-489e-bd91-e06f8c4ae2a9 +:END: + +#+findex: denote-link-to-all-files-with-contents +The aforementioned ~denote-add-links~ command takes a query that +matches it against file names ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links to all files matching a query in their file name]]). +It then creates a typographic list (bullet list) with direct links to +all the matching files. Users who wish to achieve the same result but +have the query be matched against file contents (not file names), can +use the command ~denote-link-to-all-files-with-contents~. + +The command ~denote-link-to-all-files-with-contents~ is the +counterpart of ~denote-link-to-file-with-contents~ ([[#h:25a983ca-049e-43d4-8f6e-06a2325e2c3c][Add a direct link to a file whose contents include the given query]]). + +** The ~denote-open-link-function~ user option +:PROPERTIES: +:CUSTOM_ID: h:6aa22a2d-3338-433e-ab9f-ba272417aab9 +:END: + +#+vindex: denote-open-link-function +The user option ~denote-open-link-function~ specifies the function +used by Denote to open the file of a link. The default value opens the +file in the other window. Another common value is the function +~find-file~, which will open the file in the current window. Users may +also specify a function of their choosing. + +Note that this is relevant in buffers other than Org mode because Org +has its own mechanism for how to open links (read the documentation of +the command ~org-open-at-point~). + +** The ~denote-org-store-link-to-heading~ user option +:PROPERTIES: +:CUSTOM_ID: h:d99de1fb-b1b7-4a74-8667-575636a4d6a4 +:END: + +#+vindex: denote-org-store-link-to-heading +The user option ~denote-org-store-link-to-heading~ determines whether +~org-store-link~ links to the current Org heading. + +[ Remember that what ~org-store-link~ does is merely collect a link. To + actually insert it, use the command ~org-insert-link~. Note that + ~org-capture~ uses ~org-store-link~ internally when it needs to store + a link. ] + +When the value is nil, the Denote handler for ~org-store-link~ produces +links only to the current file (by using the file's identifier). For +example: + +: [[denote:20240118T060608][Some test]] + +If the value is ~context~, the link consists of the file's identifier +and the text of the current heading, like this: + +: [[denote:20240118T060608::*Heading text][Some test::Heading text]]. + +However, if there already exists a =CUSTOM_ID= property for the +current heading, this is always given priority and is used instead of +the context. + +If the value is ~id~ or, for backward-compatibility, any other non-nil +value, then Denote will use the standard Org mechanism of the +=CUSTOM_ID= property to create a unique link to the heading. If the +heading does not have a =CUSTOM_ID=, it creates it and includes it in +its =PROPERTIES= drawer. If a =CUSTOM_ID= exists, it takes it as-is. +The result is like this: + + : [[denote:20240118T060608::#h:eed0fb8e-4cc7-478f][Some test::Heading text]] + +The value of the =CUSTOM_ID= is determined by the Org user option +~org-id-method~. The sample shown above uses the default UUID +infrastructure (though I deleted a few characters to not get +complaints from the byte compiler about long lines in the doc +string...). + +Note that this option does not affect how Org behaves with regard to +~org-id-link-to-org-use-id~. If that user option is set to create =ID= +properties, then those will be created by Org even if the Denote link +handler will take care to not use/store the =ID= value. Concretely, +users who never want =ID= properties under their headings should keep +~org-id-link-to-org-use-id~ in its nil value. + +Context links are easier to break than those with a =CUSTOM_ID= in +cases where either the heading text changes or there is another +heading that matches that text. The potential advantage of context +links is that they do not require a =PROPERTIES= drawer. + +When visiting a link to a heading, Org opens the Denote file and then +navigates to that heading. + +[ This feature only works in Org mode files, as other file types do + not have a linking mechanism that handles unique identifiers for + headings or other patterns to jump to. If ~org-store-link~ is + invoked in one such file, it captures only the Denote identifier of + the file, even if this user option is set to a non-nil value. ] + +** Adding direct links to files matching contents +:PROPERTIES: +:CUSTOM_ID: h:28cb8d14-cf56-4d73-b126-8ff269dbaa64 +:END: + +** Insert links from marked files in Dired +:PROPERTIES: +:CUSTOM_ID: h:9cbb692e-5d8a-44a6-9193-899a07872a07 +:END: + +#+findex: denote-link-dired-marked-notes +The command ~denote-link-dired-marked-notes~ is similar to +~denote-add-links~ in that it inserts in the buffer a typographic list +of links to Denote notes ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]). Though +instead of reading a regular expression, it lets the user mark files +in Dired and link to them. This should be easier for users of all +skill levels, instead of having to write a potentially complex regular +expression. + +If there are multiple buffers that visit a Denote note, this command +will ask to select one among them, using minibuffer completion. If +there is only one buffer, it will operate in it outright. If there are +no buffers, it will produce an error. + +With optional =ID-ONLY= as a prefix argument (=C-u= by default), the +command inserts links with just the identifier, which is the same +principle as with ~denote-link~ and others ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Adding a single link]]). + +The command ~denote-link-dired-marked-notes~ is meant to be used from a +Dired buffer. + +As always, links are created only for files which qualify as a "note" +for our purposes ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). + +#+findex: denote-dired-link-marked-notes +The ~denote-dired-link-marked-notes~ is an alias for ~denote-link-dired-marked-notes~. + +** Link to an existing note or create a new one +:PROPERTIES: +:CUSTOM_ID: h:b6056e6b-93df-4e6b-a778-eebd105bac46 +:END: + +In one's note-taking workflow, there may come a point where they are +expounding on a certain topic but have an idea about another subject +they would like to link to ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). The user can always rely on +the other linking facilities we have covered herein to target files that +already exist. Though they may not know whether they already have notes +covering the subject or whether they would need to write new ones. To +this end, Denote provides two convenience commands: + +#+findex: denote-link-after-creating ++ ~denote-link-after-creating~ :: Create new note in the background and + link to it directly. + + Use ~denote~ interactively to produce the new note. Its doc string or + this manual explains which prompts will be used and under what + conditions ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). + + With optional =ID-ONLY= as a prefix argument (this is the =C-u= key, + by default) create a link that consists of just the identifier. Else + try to also include the file's title. This has the same meaning as in + ~denote-link~ ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Adding a single link]]). + + IMPORTANT NOTE: Normally, ~denote~ does not save the buffer it + produces for the new note ([[#h:bf80f4cd-6f56-4f7c-a991-8573161e4511][The ~denote-save-buffer-after-creation~ option]]). + This is a safety precaution to not write to disk unless the user + wants it (e.g. the user may choose to kill the buffer, thus + cancelling the creation of the note). However, for this command the + creation of the note happens in the background and the user may miss + the step of saving their buffer. We thus have to save the buffer in + order to (i) establish valid links, and (ii) retrieve whatever front + matter from the target file. + +#+findex: denote-link-after-creating-with-command ++ ~denote-link-after-creating-with-command~ :: This command is like + ~denote-link-after-creating~ except it prompts for a note-creating + command ([[*Points of entry][Points of entry]]). Use this to, for example, call + ~denote-signature~ so that the newly created note has a signature as + part of its file name. Optional =ID-ONLY= has the same meaning as + in the command ~denote-link-after-creating~. + +#+findex: denote-link-or-create ++ ~denote-link-or-create~ :: Use ~denote-link~ on =TARGET= file, + creating it if necessary. + + If =TARGET= file does not exist, call ~denote-link-after-creating~ + which runs the ~denote~ command interactively to create the file. The + established link will then be targeting that new file. + + If =TARGET= file does not exist, add the user input that was used to + search for it to the history of the ~denote-file-prompt~. The user + can then retrieve and possibly further edit their last input, using + it as the newly created note's actual title. At the ~denote-file-prompt~ + type =M-p= with the default key bindings, which calls ~previous-history-element~. + + With optional =ID-ONLY= as a prefix argument create a link with just + the file's identifier. This has the same meaning as in ~denote-link~. + + This command has the alias ~denote-link-to-existing-or-new-note~, + which helps with discoverability. + +In all of the above, an optional prefix argument (=C-u= by default) +creates a link that consists of just the identifier. This has the +same meaning as in the regular ~denote-link~ command. + +Denote provides similar functionality for opening an existing note or +creating a new one ([[#h:ad91ca39-cf10-4e16-b224-fdf78f093883][Open an existing note or create it if missing]]). + +** The backlinks' buffer +:PROPERTIES: +:CUSTOM_ID: h:c73f1f68-e214-49d5-b369-e694f6a5d708 +:END: + +[ Older versions of Denote had two types of formatting for the + backlinks' buffer. As part of version =4.0.0=, we only support the + standard Xref view which shows matches in their context. The user + option ~denote-backlinks-show-context~ is thus removed. ] + +#+findex: denote-backlinks +#+findex: denote-show-backlinks-buffer +The command ~denote-backlinks~ (alias ~denote-show-backlinks-buffer~) +produces a bespoke buffer which displays backlinks to the current note +([[#h:435592bc-e896-429f-a599-9f1bcd5ab9b8][Interact with the links buffer]]). A "backlink" is a link back to the +present entry. Backlinks can be generated for any file type that has a +Denote file-naming scheme, such as PDFs, images, and videos, as well +as the regular plain text files. + +The backlinks' buffer is, in essence, the equivalent of a Unix ~grep~ +command across the ~denote-directory~ ([[#h:893eec49-d7be-4603-bcff-fcc247244011][Speed up backlinks' buffer creation?]]). +It groups matches by file name, while it displays the line on which a +link to the current file occurs together with its context. It looks +like this (plus the appropriate fontification): + +#+begin_example +Backlinks to "On being honest" (20220614T130812) +------------------------------------------------ + +20220614T145606--let-this-glance-become-a-stare__journal.txt +37: growing into it: [[denote:20220614T130812][On being honest]]. +64: As I said in [[denote:20220614T130812][On being honest]] I have never +20220616T182958--feeling-butterflies-in-your-stomach__journal.txt +62: indifference. In [[denote:20220614T130812][On being honest]] I alluded +#+end_example + +Note that the width of the lines in the context depends on the +underlying file. In the above example, the lines are split at the +~fill-column~. Long lines will show up just fine. Also note that the +built-in user option ~xref-truncation-width~ can truncate long lines +to a given maximum number of characters. + +As with query links, the backlinking facility uses Emacs' built-in +Xref infrastructure ([[#h:d9a84289-2f73-4ef9-b4f0-9a0aa3e9bf0d][Adding a query link]]). On some operating systems, +the user may need to add certain executables to the relevant +environment variable ([[#h:42f6b07e-5956-469a-8294-17f9cf62eb2b][Why do I get "Search failed with status 1" when I search for backlinks?]]). + +#+vindex: denote-backlinks-display-buffer-action +The placement of the backlinks' buffer is subject to the user option +~denote-backlinks-display-buffer-action~. Due to the nature of the +underlying ~display-buffer~ mechanism, this inevitably is a relatively +advanced feature. By default, the backlinks' buffer is displayed below +the current window. + +Backlinks to the current file can also be visited by using the +minibuffer completion interface with the ~denote-find-backlink~ +command ([[#h:1bc2adad-dca3-4878-b9f0-b105d5dec6f4][Visiting linked files via the minibuffer]]). + +** Writing metanotes +:PROPERTIES: +:CUSTOM_ID: h:6060a7e6-f179-4d42-a9de-a9968aaebecc +:END: + +A "metanote" is an entry that describes other entries who have something +in common. Writing metanotes can be part of a workflow where the user +periodically reviews their work in search of patterns and deeper +insights. For example, you might want to read your journal entries from +the past year to reflect on your experiences, evolution as a person, and +the like. + +The commands ~denote-add-links~, ~denote-link-dired-marked-notes~ are +suited for this task. + +[[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]. + +[[#h:9cbb692e-5d8a-44a6-9193-899a07872a07][Insert links from marked files in Dired]]. + +You will create your metanote the way you use Denote ordinarily +(metanotes may have the =metanote= keyword, among others), write an +introduction or however you want to go about it, invoke the command +which inserts multiple links at once (see the above-cited nodes), and +continue writing. + +Metanotes can serve as entry points to groupings of individual notes. +They are not the same as a filtered list of files, i.e. what you would +do in Dired or the minibuffer where you narrow the list of notes to a +given query. Metanotes contain the filtered list plus your thoughts +about it. The act of purposefully grouping notes together and +contemplating on their shared patterns is what adds value. + +Your future self will appreciate metanotes for the function they serve +in encapsulating knowledge, while current you will be equipped with the +knowledge derived from the deliberate self-reflection. + +** Visiting linked files via the minibuffer +:PROPERTIES: +:CUSTOM_ID: h:1bc2adad-dca3-4878-b9f0-b105d5dec6f4 +:END: + +#+findex: denote-find-link +Denote has a major-mode-agnostic mechanism to collect all linked file +references in the current buffer and return them as an appropriately +formatted list. This list can then be used in interactive commands. +The ~denote-find-link~ is such a command. It uses minibuffer +completion to visit a file that is linked to from the current note. +The candidates have the correct metadata, which is ideal for +integration with other standards-compliant tools ([[#h:8ed2bb6f-b5be-4711-82e9-8bee5bb06ece][Extending Denote]]). +For instance, a package such as =marginalia= will display accurate +annotations, while the =embark= package will be able to work its magic +such as in exporting the list into a filtered Dired buffer (i.e. a +familiar Dired listing with only the files of the current minibuffer +session). + +#+findex: denote-find-backlink +To visit backlinks to the current note via the minibuffer, use +~denote-find-backlink~. This is an alternative to placing backlinks +in a dedicated buffer ([[#h:c73f1f68-e214-49d5-b369-e694f6a5d708][The backlinks' buffer]]). + +** Fontify links in non-Org buffers +:PROPERTIES: +:CUSTOM_ID: h:156c5ea3-147b-4f9d-a404-86a00558c60a +:END: + +#+findex: denote-fontify-links-mode +Denote links are automatically fontified in Org buffers ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Adding a single link]]). +This means that Org recognises the link and applies the relevant +properties to it to make it clickable/actionable. Other major modes, +such as ~markdown-mode~ (for =.md= files) or ~text-mode~ (for =.txt= +files) do not have this feature built into them. Users can still get +the same behaviour as with Org by activating the ~denote-fontify-links-mode~. + +The ~denote-fontify-links-mode~ is a buffer-local minor mode. Users can enable +it automatically in plain text files that correspond to denote notes with +something like this: + +#+begin_src emacs-lisp +(add-hook 'text-mode-hook #'denote-fontify-links-mode-maybe) +#+end_src + +The ~text-mode-hook~ applies to all modes derived from ~text-mode~, including +~markdown-mode~. Though a more explicit setup does no harm: + +#+begin_src emacs-lisp +(add-hook 'markdown-mode-hook #'denote-fontify-links-mode-maybe) +#+end_src + +Because Org already recognises =denote:= links, the function +~denote-fontify-links-mode-maybe~ will not enable the mode +~denote-fontify-links-mode~ in Org buffers. + +#+findex: denote-link-markdown-follow +In files whose major mode is ~markdown-mode~, the default key binding +=C-c C-o= (which calls the command ~markdown-follow-thing-at-point~) +correctly resolves =denote:= links. Interested users can refer to the +function ~denote-link-markdown-follow~ for the implementation details. + +** The ~denote-link-description-format~ to format link descriptions +:PROPERTIES: +:CUSTOM_ID: h:f634427c-b451-40e2-993e-e00ac627af68 +:END: + +The user option ~denote-link-description-format~ controls how the +command ~denote-link~ and related functions create a link description +by default. + +The value can be either a function or a string. If it is a function, +it is called with one argument, the file, and should return a string +representing the link description. + +The default is a function that returns the active region or the title of +the note (with the signature if present). + +If the value is a string, it treats specially the following specifiers: + +- The =%t= is the Denote =TITLE= in the front matter or the file name. +- The =%T= is the Denote =TITLE= in the file name. +- The =%i= is the Denote =IDENTIFIER= of the file. +- The =%I= is the identifier converted to =DAYNAME, DAYNUM MONTHNUM + YEAR=. +- The =%d= is the same as =%i= (=DATE= mnemonic). +- The =%D= is a "do what I mean" which behaves the same as =%t= and if + that returns nothing, it falls back to =%I=, then =%i=. +- The =%d= is the same as =%i= (=DATE= mnemonic). +- The =%s= is the Denote =SIGNATURE= of the file. +- The =%k= is the Denote =KEYWORDS= of the file. +- The =%%= is a literal percent sign. + +In addition, the following flags are available for each of the specifiers: + +- 0 :: Pad to the width, if given, with zeros instead of spaces. +- - :: Pad to the width, if given, on the right instead of the left. +- < :: Truncate to the width and precision, if given, on the left. +- > :: Truncate to the width and precision, if given, on the right. +- ^ :: Convert to upper case. +- _ :: Convert to lower case. + +When combined all together, the above are written thus: + +: %SPECIFIER-CHARACTER + +Any other text in the string it taken as-is. Users may want, for +example, to include some text that makes Denote links stand out, such +as a =[D]= prefix. + +If the region is active, its text is used as the link's description. + +* Choose which commands to prompt for +:PROPERTIES: +:CUSTOM_ID: h:98c732ac-da0e-4ebd-a0e3-5c47f9075e51 +:END: + +#+vindex: denote-commands-for-new-notes +The user option ~denote-commands-for-new-notes~ specifies a list of +commands that are available at the ~denote-command-prompt~. This +prompt is used by Denote commands that ask the user how to create a +new note, as described elsewhere in this manual: + +- [[#h:ad91ca39-cf10-4e16-b224-fdf78f093883][Open an existing note or create it if missing]] +- [[#h:b6056e6b-93df-4e6b-a778-eebd105bac46][Link to a note or create it if missing]] + +The default value includes all the basic file-creating commands +([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). Users may customise this value if (i) they only +want to see fewer options and/or (ii) wish to include their own custom +command in the list ([[#h:11946562-7eb0-4925-a3b5-92d75f1f5895][Write your own convenience commands]]). + +* Fontification in Dired +:PROPERTIES: +:CUSTOM_ID: h:337f9cf0-9f66-45af-b73f-f6370472fb51 +:END: + +#+findex: denote-dired-mode +One of the upsides of Denote's file-naming scheme is the predictable +pattern it establishes, which appears as a near-tabular presentation in +a listing of notes (i.e. in Dired). The ~denote-dired-mode~ can help +enhance this impression, by fontifying the components of the file name +to make the date (identifier) and keywords stand out. + +There are two ways to set the mode. Either use it for all directories, +which probably is not needed: + +#+begin_src emacs-lisp +(add-hook 'dired-mode-hook #'denote-dired-mode) +#+end_src + +#+vindex: denote-dired-directories +#+findex: denote-dired-mode-in-directories +Or configure the user option ~denote-dired-directories~ and then set up +the function ~denote-dired-mode-in-directories~: + +#+begin_src emacs-lisp +;; We use different ways to specify a path for demo purposes. +(setq denote-dired-directories + (list denote-directory + (thread-last denote-directory (expand-file-name "attachments")) + (expand-file-name "~/Documents/vlog"))) + +(add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) +#+end_src + +#+vindex: denote-dired-directories-include-subdirectories +The user option ~denote-dired-directories-include-subdirectories~ +specifies whether the ~denote-dired-directories~ also cover their +subdirectories. By default they do not. Set this option to ~t~ to +include subdirectories as well. + +The faces we define for this purpose are: + +#+vindex: denote-faces-date +#+vindex: denote-faces-delimiter +#+vindex: denote-faces-extension +#+vindex: denote-faces-keywords +#+vindex: denote-faces-signature +#+vindex: denote-faces-subdirectory +#+vindex: denote-faces-time +#+vindex: denote-faces-title ++ ~denote-faces-date~ ++ ~denote-faces-delimiter~ ++ ~denote-faces-extension~ ++ ~denote-faces-keywords~ +- ~denote-faces-signature~ ++ ~denote-faces-subdirectory~ ++ ~denote-faces-time~ ++ ~denote-faces-title~ + +For more control, we also provide these: + +#+vindex denote-faces-year +#+vindex denote-faces-month +#+vindex denote-faces-day +#+vindex denote-faces-hour +#+vindex denote-faces-minute +#+vindex denote-faces-second ++ ~denote-faces-year~ ++ ~denote-faces-month~ ++ ~denote-faces-day~ ++ ~denote-faces-hour~ ++ ~denote-faces-minute~ ++ ~denote-faces-second~ + +For the time being, the =diredfl= package is not compatible with this +facility. + +The ~denote-dired-mode~ does not only fontify note files that were +created by Denote: it covers every file name that follows our naming +conventions ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). This is particularly useful for +scenaria where, say, one wants to organise their collection of PDFs and +multimedia in a systematic way (and, perhaps, use them as attachments +for the notes Denote produces if you are writing Org notes and are using +its standand attachments' facility). + +* Automatically rename Denote buffers +:PROPERTIES: +:CUSTOM_ID: h:3ca4db16-8f26-4d7d-b748-bac48ae32d69 +:END: + +#+findex: denote-rename-buffer-mode +The minor mode ~denote-rename-buffer-mode~ provides the means to +automatically rename the buffer of a Denote file upon visiting the +file. This applies both to existing Denote files as well as new ones +([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). Enable the mode thus: + +#+begin_src emacs-lisp +(denote-rename-buffer-mode 1) +#+end_src + +#+vindex: denote-rename-buffer-function +#+findex: denote-rename-buffer +#+vindex: denote-rename-buffer-format +Buffers are named by applying the function specified in the user +option ~denote-rename-buffer-function~. The default function is +~denote-rename-buffer~: it renames the buffer based on the template +set in the user option ~denote-rename-buffer-format~. By default, the +formatting template targets only the =TITLE= component of the file +name ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). Other fields are explained elsewhere in +this manual ([[#h:35507c18-35b1-41b9-9d80-52f54fcef3cb][The denote-rename-buffer-format]]). + +Note that renaming a buffer is not the same as renaming a file +([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). The former is just for convenience inside of Emacs. +Whereas the latter is for writing changes to disk, making them +available to all programs. + +** The ~denote-rename-buffer-format~ option +:PROPERTIES: +:CUSTOM_ID: h:35507c18-35b1-41b9-9d80-52f54fcef3cb +:END: + +The user option ~denote-rename-buffer-format~ controls how the +function ~denote-rename-buffer~ chooses the name of the +buffer-to-be-renamed. + +The value of this user option is a string. The following specifiers +are placeholders for Denote file name components ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]): + +#+vindex: denote-rename-buffer-backlinks-indicator +- The =%t= is the Denote =TITLE= in the front matter or the file name. +- The =%T= is the Denote =TITLE= in the file name. +- The =%i= is the Denote =IDENTIFIER= of the file. +- The =%I= is the identifier converted to =DAYNAME, DAYNUM MONTHNUM + YEAR=. +- The =%d= is the same as =%i= (=DATE= mnemonic). +- The =%D= is a "do what I mean" which behaves the same as =%t= and if + that returns nothing, it falls back to =%I=, then =%i=. +- The =%s= is the Denote =SIGNATURE= of the file. +- The =%k= is the Denote =KEYWORDS= of the file. +- The =%b= is an indicator of whether or not the file has backlinks + pointing to it. The indicator string is defined in the user option + ~denote-rename-buffer-backlinks-indicator~, alias + ~denote-buffer-has-backlinks-string~. +- The =%%= is a literal percent sign. + +In addition, the following flags are available for each of the specifiers: + +- =0= :: Pad to the width, if given, with zeros instead of spaces. +- =-= :: Pad to the width, if given, on the right instead of the left. +- =<= :: Truncate to the width and precision, if given, on the left. +- =>= :: Truncate to the width and precision, if given, on the right. +- =^= :: Convert to upper case. +- =_= :: Convert to lower case. + +When combined all together, the above are written thus: + +: %SPECIFIER-CHARACTER + +Any other string it taken as-is. Users may want, for example, to +include some text that makes Denote buffers stand out, such as +a =[D]= prefix. Examples: + +#+begin_src emacs-lisp +;; The following is the default value. Use a literal [D] prefix, +;; followed by the title and then the backlinks indicator. If there +;; is no title, use the identifier in its human-readable date +;; representation, and if that is not possible, use the identifier +;; as-is. +(setq denote-rename-buffer-format "[D] %D%b") + +;; Customize what the backlink indicator looks like. This two-faced +;; arrow is the default. +(setq denote-rename-buffer-backlinks-indicator "<-->") + +;; Use just the title and keywords with some emoji in between, because +;; why not? +(setq denote-rename-buffer-format "%t 🤨 %k") + +;; Use the title with a literal "[D]" before it. +(setq denote-rename-buffer-format "[D] %t") + +;; As above, but also add the `denote-rename-buffer-backlinks-indicator' at the end. +(setq denote-rename-buffer-format "[D] %t%b") +#+end_src + +Users who need yet more flexibility are best served by writing their +own function and assigning it to the ~denote-rename-buffer-function~. + +* Use Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:8b542c50-dcc9-4bca-8037-a36599b22779 +:END: + +This section is about the external package ~denote-org~ (by +Protesilaos). The code of ~denote-org~ used to be available as part of +the main ~denote~ package, but we decided to keep each optional +extension as a separate package to make things easier to maintain and +to understand. + +Denote can optionally integrate with Org mode's "dynamic blocks" +facility. This means that it can use special blocks that are evaluated +with =C-c C-x C-u= (~org-dblock-update~) to generate their contents. + +Dynamic blocks are particularly useful for metanote entries that +reflect on the status of earlier notes ([[#h:6060a7e6-f179-4d42-a9de-a9968aaebecc][Writing metanotes]]). The +~denote-org~ package defines many of these Org dynamic blocks. + ++ Package name (GNU ELPA): ~denote-org~ ++ Official manual: ++ Git repository: ++ Backronym: Denote... Ordinarily Restricts Gyrations. + +* Display filtered and sorted files with ~denote-sort-dired~ or ~denote-dired~ +:PROPERTIES: +:CUSTOM_ID: h:9fe01e63-f34f-4479-8713-f162a5ca865e +:END: + +The =denote.el= file contains functions which empower user or +developers to sort files by the given file name component ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+findex: denote-sort-dired +#+findex: denote-dired +The command ~denote-sort-dired~ (alias ~denote-dired~) produces a +Dired file listing with a flat, filtered, and sorted set of files from +the ~denote-directory~ ([[#h:c958e087-1d23-4a25-afdd-db7bf5606b4c][Define a sorting function per component]]). It +does so by a series of prompts, which can be configured with the user +option ~denote-sort-dired-extra-prompts~ ([[#h:a34228cb-484f-48fe-9cbc-8e41f313127b][Configure what extra prompts ~denote-sort-dired~ issues]]). + +Think of ~denote-sort-dired~ as the counterpart to the Unix ~find~ +command. While ~denote-grep~ corresponds to the Unix ~grep~ ([[#h:e71c9d14-7e88-4386-91d0-9ad249947077][Use ~denote-grep~ to search inside files]]). + +The out-of-the-box behaviour of ~denote-sort-dired~ is as follows: + +1. It first asks for a regular expression with which to match Denote + file names. Remember that due to Denote's efficient file-naming + scheme, you usually do not need to write some complex regular + expression. For example, something like =_journal= will match only + files with a =journal= keyword. +2. Once the regular expression is provided, the command asks for a + Denote file name component to sort files by. This is a symbol among + =title=, =keywords=, =signature=, and =identifier= ([[#h:c958e087-1d23-4a25-afdd-db7bf5606b4c][Define a sorting function per component]]). +3. Finally, it asks a "yes or no" on whether to reverse the sort order. + +The resulting listing is a regular Dired buffer, unlike that of +~dired-virtual-mode~ ([[#h:d35d8d41-f51b-4139-af8f-9c8cc508e35b][Use ~dired-virtual-mode~ for arbitrary file listings]]). + +#+findex: denote-sort-files +The sorting mechanism can be used by other packages to achieve their +ends. As an example, the dynamic Org blocks that the ~denote-org~ +package (by Protesilaos) defines also use this feature internally by +means of the non-interactive function ~denote-sort-files~. + +** Configure what extra prompts ~denote-sort-dired~ issues +:PROPERTIES: +:CUSTOM_ID: h:a34228cb-484f-48fe-9cbc-8e41f313127b +:END: + +#+vindex: denote-sort-dired-extra-prompts +By default, the ~denote-sort-dired~ command prompts for (i) a query to +match file names, (ii) a file name component to sort by, and (iii) +whether to reverse the sorting ([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Display filtered and sorted files with denote-sort-dired]]). +Users can configure the latter two by modifying the user option +~denote-sort-dired-extra-prompts~. + +The ~denote-sort-dired-extra-prompts~ accepts either a nil value or a +list of symbols among ~sort-by-component~, ~reverse-sort~, and +~exclude-regexp~. The order those symbols appear in the list is +significant, with the leftmost coming first. + +These symbols correspond to the following: + +- A choice to select the file name component to sort by. +- A yes or no prompt on whether to reverse the sorting. +- A string (or regular expression) of files to be excluded from the + results. + +#+vindex: denote-sort-dired-default-sort-component +#+vindex: denote-sort-dired-default-reverse-sort +In case of a nil value, those extra prompts will not happen, meaning +that ~denote-sort-dired~ will fall back to using whatever is defined +in the variables ~denote-sort-dired-default-sort-component~ and +~denote-sort-dired-default-reverse-sort~. + +Here are some examples: + +#+begin_src emacs-lisp +;; The default extra prompts... +(setq denote-sort-dired-extra-prompts '(sort-by-component reverse-sort)) + +;; When using `denote-sort-dired', ask whether to reverse the sort and +;; then which file name component to sort by. These are always done +;; after the prompt to search for files matching a regexp. +(setq denote-sort-dired-extra-prompts '(reverse-sort sort-by-component)) + +;; Do not prompt for a reverse sort. Just use the value of +;; `denote-sort-dired-default-reverse-sort' (which is nil out-of-the-box). +(setq denote-sort-dired-extra-prompts '(sort-by-component)) + +;; Do not issue any extra prompts. Always sort by the `title' file +;; name component and never do a reverse sort. +(setq denote-sort-dired-extra-prompts nil) +(setq denote-sort-dired-default-sort-component 'title) +(setq denote-sort-dired-default-reverse-sort nil) +#+end_src + +** Define a sorting function per component +:PROPERTIES: +:CUSTOM_ID: h:c958e087-1d23-4a25-afdd-db7bf5606b4c +:END: + +When sorting by =title=, =keywords=, or =signature= with the +~denote-sort-dired~ command, Denote will internally apply a sorting +function that is specific to each component ([[#h:a34228cb-484f-48fe-9cbc-8e41f313127b][Configure what extra prompts ~denote-sort-dired~ issues]]). +These are subject to user configuration: + +#+vindex: denote-sort-identifier-comparison-function +- ~denote-sort-identifier-comparison-function~ + +#+vindex: denote-sort-title-comparison-function +- ~denote-sort-title-comparison-function~ + +#+vindex: denote-sort-keywords-comparison-function +- ~denote-sort-keywords-comparison-function~ + +#+vindex: denote-sort-signature-comparison-function +- ~denote-sort-signature-comparison-function~ + +By default, all these user options use the same sorting function, +namely ~string-collate-lessp~. Users who have specific needs for any +of those file name components can write their own sorting algorithms +([[#h:95345870-4ccd-484f-9adf-de4747ad5760][Sort signatures that include Luhmann-style sequences]]). + +*** Sort signatures that include Luhmann-style sequences +:PROPERTIES: +:CUSTOM_ID: h:95345870-4ccd-484f-9adf-de4747ad5760 +:END: + +[ The ~denote-sequence~ package (by Protesilaos) covers this use-case + and many others ([[#h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f][Write sequence notes or folgezettel]]). It is the + superior option for anyone interested in this functionality. We keep + the code below for reference, as there may be users of it who need + to revisit it. Though long-term, it is better to use ~denote-sequence~. ] + +Niklas Luhmann would edit notes to form sequences of thoughts with +branching paths, such as =1.1=, =1.1a=, =1.2=, =1.2a=, =1.2b=, etc. +With the Denote file-naming scheme, we make the word separator in each +file name component use the same character as the entire field, so +words in a title have a dash between them and signatures have the +equals sign ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). Thus, our Luhmann-style +signature will be slightly different in their looks: ~1=1~, ~1=1a~, +~1=2~, ~1=2a~, ~1=2b~. + +When using the ~denote-sort-dired~ command with default settings, our +signatures will not sort in an intuitive way. This is because they +combine numbers and letters, which require a different approach than +what the default sorting function is using ([[#h:c958e087-1d23-4a25-afdd-db7bf5606b4c][Define a sorting function per component]]). +In the following code block, we show a sorting algorithm that should +do the right thing while dealing with Luhmann-style signatures. + +#+begin_src emacs-lisp +(defun my-denote--split-luhman-sig (signature) + "Split numbers and letters in Luhmann-style SIGNATURE string." + (replace-regexp-in-string + "\\([a-zA-Z]+?\\)\\([0-9]\\)" "\\1=\\2" + (replace-regexp-in-string + "\\([0-9]+?\\)\\([a-zA-Z]\\)" "\\1=\\2" + signature))) + +(defun my-denote--pad-sig (signature) + "Create a new signature with padded spaces for all components" + (combine-and-quote-strings + (mapcar + (lambda (x) + (string-pad x 5 32 t)) + (split-string (my-denote--split-luhman-sig signature) "=" t)) + "=")) + +(defun my-denote-sort-for-signatures (sig1 sig2) + "Return non-nil if SIG1 is smaller that SIG2. +Perform the comparison with `string<'." + (string< (my-denote--pad-sig sig1) (my-denote--pad-sig sig2))) + +;; Change the sorting function only when we sort by signature. +(setq denote-sort-signature-comparison-function #'my-denote-sort-for-signatures) +#+end_src + +* Use ~denote-grep~ to search inside files +:PROPERTIES: +:CUSTOM_ID: h:e71c9d14-7e88-4386-91d0-9ad249947077 +:END: + +#+findex: denote-grep +The command ~denote-grep~ searches for the given query across all +readable files in the ~denote-directory~. It puts the collected +results in an Xref buffer (just like with our backlinks and query +links functionality). In this buffer, users can do =M-x describe-mode= +(=C-h m= with default key bindings) to learn about all the actions +they can perform and the keys they are bound to ([[#h:435592bc-e896-429f-a599-9f1bcd5ab9b8][Interact with the links buffer]]). + +Think of ~denote-grep~ as the counterpart to the Unix ~grep~ command. +While ~denote-sort-dired~ corresponds to the Unix ~find~ ([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Display filtered and sorted files with ~denote-sort-dired~]]). + +#+findex: denote-grep-marked-dired-files +The command ~denote-grep-marked-dired-files~ is like ~denote-grep~ but +operates on the files that are marked in a Dired buffer. + +#+findex: denote-grep-files-referenced-in-region +The command ~denote-grep-files-referenced-in-region~ is like +~denote-grep~ for any files referenced within the boundaries of the +marked region. Files are referenced by their identifier. This includes +links with just the identifier (as described in ~denote-link~ and +related ([[#h:5e5e3370-12ab-454f-ba09-88ff44214324][Add a single direct link using a file name prompt]])), links +written by an Org dynamic block (see the ~denote-org~ package ([[#h:8b542c50-dcc9-4bca-8037-a36599b22779][Use Org dynamic blocks]])), +or even file listings such as those of ~dired~ and the command-line +~ls~ program. + +#+vindex: denote-grep-display-buffer-action +The user option ~denote-grep-display-buffer-action~ controls where the +buffer with the search results is displayed at. By default, they appear in +the same window where the command ~denote-grep~ is called from. + +* Interact with the links buffer +:PROPERTIES: +:CUSTOM_ID: h:435592bc-e896-429f-a599-9f1bcd5ab9b8 +:END: + +Denote commands, such as ~denote-grep~, ~denote-backlinks~, and +~denote-query-contents-link~, produce an Xref buffer with search +results ([[#h:893eec49-d7be-4603-bcff-fcc247244011][Speed up backlinks' or query links' buffer creation?]]). +Matching lines are grouped by the file name they belong to. + +- [[#h:e71c9d14-7e88-4386-91d0-9ad249947077][Use ~denote-grep~ to search inside files]]. +- [[#h:c73f1f68-e214-49d5-b369-e694f6a5d708][The backlinks' buffer]]. +- [[#h:d9a84289-2f73-4ef9-b4f0-9a0aa3e9bf0d][Add a query link]]. + +#+findex: denote-query-mode +#+vindex: denote-query-mode-map +This buffer uses the major mode ~denote-query-mode~. It binds commands +to keys in the ~denote-query-mode-map~. Those allow users to filter +the output of the last search. Here, "last search" refers to the list +of files that were returned by whichever command produced the buffer +(e.g. the last ~denote-grep~). + +#+findex: denote-query-focus-last-search +- ~denote-query-focus-last-search~ :: Perform a search in the contents + of files that were matched by the last search. + +#+findex: denote-query-exclude-files +- ~denote-query-exclude-files~ :: Exclude files from the last search + whose name matches the given input. + +#+findex: denote-query-only-include-files +- ~denote-query-only-include-files~ :: Only keep files from the last + search whose name matches the given input. + +#+findex: denote-query-exclude-files-with-keywords +- ~denote-query-exclude-files-with-keywords~ :: Exclude files from the + last search whose name includes the given keywords. + +#+findex: denote-query-only-include-files-with-keywords +- ~denote-query-only-include-files-with-keywords~ :: Only keep files + from the last search whose name includes the given keywords. + +#+findex: denote-query-clear-all-filters +- ~denote-query-clear-all-filters~ :: Clear all the applied filters. + +Remember that these are easy to use even without knowledge of regular +expressions, thanks to the efficiency of the Denote file-naming scheme +([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). For +instance, to exclude notes with the keyword =philosophy= from current +search buffer, use ~denote-query-exclude-files~ and then type +=_philosophy= as your input. + +In addition to those filtering options, the ~denote-query-mode~ also +allows provides an outline mechanism to hide or show the matches as +these are grouped per file. There also are some of the default actions +provided by the Xref infrastructure. Users can do =M-x describe-mode= +(=C-h m= with default key bindings) to learn about all the actions +they can perform. + +* Minibuffer histories +:PROPERTIES: +:CUSTOM_ID: h:82dc1203-d689-44b2-9a6c-b37776209651 +:END: + +Denote has a dedicated minibuffer history for each one of its prompts. +This practically means that using =M-p= (~previous-history-element~) and +=M-n= (~next-history-element~) will only cycle through the relevant +record of inputs, such as your latest titles in the =TITLE= prompt, and +keywords in the =KEYWORDS= prompt. + +The built-in =savehist= library saves minibuffer histories. Sample +configuration: + +#+begin_src emacs-lisp +(require 'savehist) +(setq savehist-file (locate-user-emacs-file "savehist")) +(setq history-length 500) +(setq history-delete-duplicates t) +(setq savehist-save-minibuffer-history t) +(add-hook 'after-init-hook #'savehist-mode) +#+end_src + +* Packages that build on Denote +:PROPERTIES: +:CUSTOM_ID: h:08b14682-e73f-4b11-b2e2-be3b788c8572 +:END: + +This is a list of packages that extend Denote. If you are a package +author, please let us know about your work and we will include it +here (either use the Git repositories or email Protesilaos directly). + +** Use the ~consult-denote~ package for enhanced minibuffer interactions +:PROPERTIES: +:CUSTOM_ID: h:113a13e1-35da-46f1-9414-81e9be2facc1 +:END: + +The ~consult-denote~ package by me (Protesilaos) integrates Denote +with Daniel Mendler's ~consult~ package: . + +The idea is to preserve the familiar patterns of interaction with the +various Denote commands but add to them an extra layer of +functionality, such as the preview mechanism that Consult provides +(e.g. preview the file you are about to link to). + +Additionally, ~consult-denote~ defines new "sources" for the +~consult-buffer~ command. This command provides a single point of +entry for buffers, recently opened files, and bookmarks. With +~consult-denote~, it has a dedicated place for Denote-specific +buffers, silos, and more (all of which are configurable). + +Unlike the ~consult-notes~ package by Colin McLear, ~consult-denote~ +uses the same presentation of data in the minibuffer to stay in sync +with Denote and make its feature set entirely optional ([[#h:8907f4bc-992a-45bc-a60e-267ed1ce9c2d][Use the ~consult-notes~ package]]). +It also only works with Denote. + +** Use the ~denote-sequence~ package to write sequence notes or "folgezettel" +:PROPERTIES: +:CUSTOM_ID: h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f +:ALT_TITLE: Sequence notes +:END: + +This section is about the external package ~denote-sequence~ (by +Protesilaos). The original idea was to include the code as part of the +~denote~ package, but we decided to keep each optional extension as a +separate package to make things easier to maintain and to understand. + +Denote defines an optional file name component called the =SIGNATURE= +([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). This is a free-form field that users can +fill in with whatever text they want, such as to have a video split up +into =part1= and =part2=, or to set some kind of priority like =a= and +=b=, or even to have a special tag that stands out from the rest of +the keywords. + +A more specialised use-case of the =SIGNATURE= is to define a +hierarchical relationship between notes, such that the thoughts they +expound on form sequences. For example, an article about the Labrador +Retriever dog breed is a continuation of a thought process that +extends something about dog breeds in general which, in turn, is a +topic that belongs to the wider theme of dogs. + +The ~denote-sequence~ package has a manual that explains these +concepts and relevant commands in further detail: + ++ Package name (GNU ELPA): ~denote-sequence~ ++ Official manual: ++ Git repository: ++ Backronym: Denote... Sequences Efficiently Queue Unsorted Entries + Notwithstanding Curation Efforts. + +** Use the ~denote-markdown~ package to better integrate Markdown with Denote +:PROPERTIES: +:CUSTOM_ID: h:79b614e7-d7a3-4953-b776-216e6e1ede77 +:END: + +The ~denote-markdown~ package (by Protesilaos) provides some +convenience functions to better integrate Markdown with Deonte. This +is mostly about converting links from one type to another so that they +can work in different applications (because Markdown does not have a +standardised way to define custom link types). + +The code of ~denote-markdown~ used to be bundled up with the ~denote~ +package before version =4.0.0= of the latter and was available in the +file =denote-md-extras.el=. Users of the old code will need to adapt +their setup to use the ~denote-markdown~ package. This can be done by +replacing all instances of =denote-md-extras= with =denote-markdown= +across their configuration. + ++ Package name (GNU ELPA): ~denote-markdown~ ++ Official manual: ++ Git repository: ++ Backronyms: Denote... Markdown's Ambitious Reimplimentations + Knowingly Dilute Obvious Widespread Norms; Denote... Markup + Agnosticism Requires Knowhow to Do Only What's Necessary. + +** Use the ~denote-journal~ package which was formerly =denote-journal-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92 +:END: + +The ~denote-journal~ package (by Protesilaos) makes it easier to use +Denote for journaling. While it is possible to use the generic +~denote~ command (and related) to maintain a journal, this package +defines extra functionality to streamline the journaling workflow. + +The code of ~denote-journal~ used to be bundled up with the ~denote~ +package before version =4.0.0= of the latter and was available in the +file =denote-journal-extras.el=. Users of the old code will need to +adapt their setup to use the ~denote-journal~ package. This can be +done by replacing all instances of =denote-journal-extras= with +=denote-journal= across their configuration. + ++ Package name (GNU ELPA): ~denote-journal~ ++ Official manual: ++ Git repository: ++ Backronym: Denote... Journaling Obviously Utilises Reasonableness + Notwithstanding Affectionate Longing. + +** Use the ~denote-silo~ package which formerly was =denote-silo-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:e43baf95-f201-4fec-8620-c0eb5eaa1c85 +:END: + +The ~denote-silo~ package (by Protesilaos) provides convenience +functions for working with silos ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]). + +The code of ~denote-silo~ used to be bundled up with the ~denote~ +package before version =4.0.0= of the latter and was available in the +file =denote-silo-extras.el=. Users of the old code will need to adapt +their setup to use the ~denote-silo~ package. This can be done by +replacing all instances of =denote-silo-extras= with =denote-silo= +across their configuration. + ++ Package name (GNU ELPA): ~denote-silo~ ++ Official manual: ++ Git repository: ++ Backronym: Denote... Silos Insulate Localised Objects. + +** Use the ~denote-search~ package as a search interface +:PROPERTIES: +:CUSTOM_ID: h:c905b733-e959-4aa4-8f2c-0ed9eba459df +:END: + +[ As part of version =4.0.0=, Denote comes with the ~denote-grep~ + command and related functionality ([[#h:e71c9d14-7e88-4386-91d0-9ad249947077][Use ~denote-grep~ to search inside files]]). + The core of this feature set was written by Lucas Quintana. ] + +The ~denote-search~ package by Lucas Quintana provides a search +utility for Denote: . + +It allows you to search for a regular expression in the content of +your notes. Its main advantages over other similar tools are the +possibility of filtering the results by file name and doing further +searches in the files matched previously. This allows for advanced +usage (think about finding a note with two or three specific words in +different lines and with a specific keyword). More features are +described in its comprehensive manual. ~denote-search~ builds upon +standard Emacs libraries, namely Xref, and so it doesn't have external +dependencies other than Denote itself. + +** Use the ~denote-explore~ package to explore your notes +:PROPERTIES: +:CUSTOM_ID: h:110ae3a4-5fc6-45da-b9bb-0e294bd12981 +:END: + +Peter Prevos has developed the ~denote-explore~ package which provides +four groups of Emacs commands to explore your Denote files: + +- Summary statistics :: Count notes, attachments and keywords. +- Random walks :: Generate new ideas using serendipity. +- Janitor :: Manage your denote collection. +- Visualisations :: Visualise your Denote network. + +The package's documentation covers the details: +. + +** Use the ~citar-denote~ package for bibliography notes +:PROPERTIES: +:CUSTOM_ID: h:226d66e4-b7de-4617-87e2-a7f2d6f007dd +:END: + +Peter Prevos has produced the ~citar-denote~ package which makes it +possible to write notes on BibTeX entries with the help of the ~citar~ +package. These notes have the citation's unique key associated with +them in the file's front matter. They also get a configurable keyword +in their file name, making it easy to find them in Dired and/or +retrieve them with the various Denote methods. + +With ~citar-denote~, the user leverages standard minibuffer completion +mechanisms (e.g. with the help of the ~vertico~ and ~embark~ packages) +to manage bibliographic notes and access those notes with ease. The +package's documentation covers the details: . + +** Use the ~consult-notes~ package +:PROPERTIES: +:CUSTOM_ID: h:8907f4bc-992a-45bc-a60e-267ed1ce9c2d +:END: + +[ Also check the ~consult-denote~ package by me (Protesilaos): + [[#h:113a13e1-35da-46f1-9414-81e9be2facc1][Use the ~consult-denote~ package for enhanced minibuffer interactions]]. ] + +If you are using Daniel Mendler's ~consult~ (which is a brilliant +package), you will most probably like its ~consult-notes~ extension, +developed by Colin McLear. It uses the familiar mechanisms of Consult +to preview the currently selected entry and to filter searches via a +prefix key. For example: + +#+begin_src emacs-lisp +(setq consult-notes-file-dir-sources + `(("Denote Notes" ?d ,(denote-directory)) + ("Books" ?b "~/Documents/books/"))) +#+end_src + +With the above, =M-x consult-notes= will list the files in those two +directories. If you type =d= and space, it narrows the list to just +the notes, while =b= does the same for books. + +The other approach is to enable the ~consult-notes-denote-mode~. It +takes care to add the ~denote-directory~ to the sources that +~consult-notes~ reads from. Denote notes are then filtered by the =d= +prefix followed by a space. + +The minor mode has the extra feature of reformatting the title of +notes shown in the minibuffer. It isolates the =TITLE= component of +each note and shows it without hyphens, while presenting keywords in +their own column. The user option ~consult-notes-denote-display-id~ +can be set to ~nil~ to hide the identifier. Depending on how one +searches through their notes, this refashioned presentation may be the +best option ([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). + +** Use the ~denote-menu~ package +:PROPERTIES: +:CUSTOM_ID: h:472db709-27de-4a1f-a171-c3fe0a7a9be8 +:END: + +Denote's file-naming scheme is designed to be efficient and to provide +valueable meta information about the file. The cost, however, is that +it is terse and harder to read, depending on how the user chooses to +filter and process their notes. + +To this end, [[https://github.com/namilus/denote-menu][the ~denote-menu~ package by Mohamed Suliman]] provides the +convenience of a nice tabular interface for all notes. ~denote-menu~ +removes the delimiters that are found in Denote file names and +presents the information in a human-readable format. Furthermore, the +package provides commands to interact with the list of notes, such as +to filter them and to transition from the tabular list to Dired. Its +documentation expands on the technicalities. + +** Use the ~denote-zettel-interface~ package +:PROPERTIES: +:CUSTOM_ID: h:b57f73fc-ac4d-4df2-bd7a-8d47cb202647 +:END: + +The [[https://github.com/krisbalintona/denote-zettel-interface][~denote-zettel-interface~ package by Kristoffer Balintona]] is +designed for those who want to use Denote while adhering to a strict +Zettelkasten methodology of sequence notes (Folgezettel). This method +leverages the optional =SIGNATURE= file name component of Denote ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). +The package provides a point of entry to one's note by visualising +them in a tabulated (grid) interface. Files are sorted by their +Folgezettel index. Users can then use a number of commands to filter +their files, navigate around, and the like. + +Note that the package is in early development as of this writing +(2024-12-03 10:18 +0200). + +* Extending Denote +:PROPERTIES: +:CUSTOM_ID: h:8ed2bb6f-b5be-4711-82e9-8bee5bb06ece +:END: + +Denote is a tool with a narrow scope: create notes and link between +them, based on the aforementioned file-naming scheme. For other common +operations the user is advised to rely on standard Emacs facilities or +specialised third-party packages ([[#h:08b14682-e73f-4b11-b2e2-be3b788c8572][Packages that build on Denote]]). This +section covers the details. + +** Access the data of the latest note +:PROPERTIES: +:CUSTOM_ID: h:a947908e-1847-4471-ba07-377ee2f4b36c +:END: + +#+vindex: denote-current-data +The variable ~denote-current-data~ is updated each time a new note is +created as well as after a rename operation. + +This is an alist where each ~car~ is one among ~title~, ~keywords~, +~signature~, ~directory~, ~date~, ~id~, ~file-type~, ~template~. The +value each of them contains is the unprocessed input (e.g. the title +before it is sluggified). + +Users who need to access this data as part of their custom code can +rely on the hooks ~denote-after-new-note-hook~ and +~denote-after-rename-file-hook~. + +** Create a new note in any directory +:PROPERTIES: +:CUSTOM_ID: h:c9d7c157-85a6-4bb7-bd21-d00bccf5e799 +:END: + +The commands that create new files are designed to write to the +~denote-directory~. The idea is that the linking mechanism can find +any file by its identifier if it is in the ~denote-directory~ +(searching the entire file system would be cumbersome). + +However, these are cases where the user needs to create a new note in +an arbitrary directory. The following command can do this. Put the +code in your configuration file and evaluate it. Then call the command +by its name with =M-x=. + +#+begin_src emacs-lisp +(defun my-denote-create-note-in-any-directory () + "Create new Denote note in any directory. +Prompt for the directory using minibuffer completion." + (declare (interactive-only t)) + (interactive) + (let ((denote-directory (read-directory-name "New note in: " nil nil :must-match))) + (call-interactively 'denote))) +#+end_src + +** Find empty notes and put them in a Dired buffer +:PROPERTIES: +:CUSTOM_ID: h:bbc7a769-19e8-4598-a2b7-06e1d673ae80 +:END: + +[ This feature is based on the command ~denote-sort-dired~ ([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Sort files by component]]). ] + +Users may have a workflow where they use the commands +~denote-link-or-create~ or ~denote-link-after-creating~ (and related) +to produce new notes that they plan to elaborate on later ([[#h:b6056e6b-93df-4e6b-a778-eebd105bac46][Link to an existing note or create a new one]]). + +To help users find those empty notes, we document the following +commands: + +- ~my-denote-sort-dired-empty-files~ +- ~my-denote-sort-dired-without-empty-files~ +- ~my-denote-sort-dired-all-empty-files~ +- ~my-denote-sort-dired-without-all-empty-files~ + +#+begin_src emacs-lisp +(require 'denote-sort) + +(defun my-denote--note-has-no-contents-p (file) + "Return non-nil if FILE is an empty note. +This means that FILE conforms with `denote-file-is-note-p' and either +has no contents or has only the front matter." + (and (denote-file-is-note-p file) + (or (denote--file-with-temp-buffer file + (re-search-forward "^$" nil t) + (if (re-search-forward "[^\s\t\n\r]+" nil t) + nil + t)) + ;; This must come later because here we consider a file + ;; "empty" even if it only has front matter. + (denote--file-empty-p file)))) + +(defun my-denote-sort-dired-empty-files (files-matching-regexp sort-by-component reverse) + "Like `denote-sort-dired' but only cover empty files. +Empty files are those that satisfy `my-denote--note-has-no-contents-p'." + (interactive + (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts))) + (let ((component (or sort-by-component + denote-sort-dired-default-sort-component + 'identifier)) + (reverse-sort (or reverse + denote-sort-dired-default-reverse-sort + nil))) + (if-let* ((default-directory (denote-directory)) + (files (denote-sort-get-directory-files files-matching-regexp component reverse-sort)) + (empty-files (seq-filter #'my-denote--note-has-no-contents-p files)) + ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as + ;; buffer-name produces an error if the regexp contains a + ;; wildcard for a directory. I can reproduce this in emacs + ;; -Q and am not sure if it is a bug. Anyway, I will report + ;; it upstream, but even if it is fixed we cannot use it + ;; for now (whatever fix will be available for Emacs 30+). + (denote-sort-dired-buffer-name (format "Denote sort `%s' by `%s'" files-matching-regexp component)) + (buffer-name (format "Denote sort by `%s' at %s" component (format-time-string "%T")))) + (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name empty-files))))) + (setq denote-sort--dired-buffer dired-buffer) + (with-current-buffer dired-buffer + (setq-local revert-buffer-function + (lambda (&rest _) + (kill-buffer dired-buffer) + (denote-sort-dired files-matching-regexp component reverse-sort)))) + ;; Because of the above NOTE, I am printing a message. Not + ;; what I want, but it is better than nothing... + (message denote-sort-dired-buffer-name)) + (message "No matching files for: %s" files-matching-regexp)))) + +(defun my-denote-sort-dired-without-empty-files (files-matching-regexp sort-by-component reverse) + "Like `denote-sort-dired' but only cover empty files. +Empty files are those that satisfy `my-denote--note-has-no-contents-p'." + (interactive + (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts))) + (let ((component (or sort-by-component + denote-sort-dired-default-sort-component + 'identifier)) + (reverse-sort (or reverse + denote-sort-dired-default-reverse-sort + nil))) + (if-let* ((default-directory (denote-directory)) + (files (denote-sort-get-directory-files files-matching-regexp component reverse-sort)) + (empty-files (seq-remove #'my-denote--note-has-no-contents-p files)) + ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as + ;; buffer-name produces an error if the regexp contains a + ;; wildcard for a directory. I can reproduce this in emacs + ;; -Q and am not sure if it is a bug. Anyway, I will report + ;; it upstream, but even if it is fixed we cannot use it + ;; for now (whatever fix will be available for Emacs 30+). + (denote-sort-dired-buffer-name (format "Denote sort `%s' by `%s'" files-matching-regexp component)) + (buffer-name (format "Denote sort by `%s' at %s" component (format-time-string "%T")))) + (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name empty-files))))) + (setq denote-sort--dired-buffer dired-buffer) + (with-current-buffer dired-buffer + (setq-local revert-buffer-function + (lambda (&rest _) + (kill-buffer dired-buffer) + (denote-sort-dired files-matching-regexp component reverse-sort)))) + ;; Because of the above NOTE, I am printing a message. Not + ;; what I want, but it is better than nothing... + (message denote-sort-dired-buffer-name)) + (message "No matching files for: %s" files-matching-regexp)))) + +(defun my-denote-sort-dired-all-empty-files () + "List all empty files in a Dired buffer. +This is the same as calling `my-denote-sort-dired' with a +FILES-MATCHING-REGEXP of \".*\"." + (declare (interactive-only t)) + (interactive) + (let* ((other-prompts (denote-sort-dired--prompts)) + (sort-key (nth 1 other-prompts)) + (reverse (nth 2 other-prompts))) + (funcall-interactively #'my-denote-sort-dired-empty-files ".*" sort-key reverse))) + +(defun my-denote-sort-dired-without-all-empty-files () + "List all empty files in a Dired buffer. +This is the same as calling `my-denote-sort-dired' with a +FILES-MATCHING-REGEXP of \".*\"." + (declare (interactive-only t)) + (interactive) + (let* ((other-prompts (denote-sort-dired--prompts)) + (sort-key (nth 1 other-prompts)) + (reverse (nth 2 other-prompts))) + (funcall-interactively #'my-denote-sort-dired-without-empty-files ".*" sort-key reverse))) +#+end_src + +[ In the above snippet, I am purposefully duplicating code to make it + easier for users to pick the ones they need. ] + +** Automatically rename the note after saving it +:PROPERTIES: +:CUSTOM_ID: h:c7d4dd3a-38bb-4f1c-a36e-989ec0bc79a6 +:END: + +While experimenting with Denote, users may need to try different +workflows to figure out what works for them. Those might involve +changing keywords and specifying titles in a particular way. The +following sample can be used: + +#+begin_src emacs-lisp +(defun my-denote-always-rename-on-save-based-on-front-matter () + "Rename the current Denote file, if needed, upon saving the file. +Rename the file based on its front matter, checking for changes in the +title or keywords fields. + +Add this function to the `after-save-hook'." + (let ((denote-rename-confirmations nil) + (denote-save-buffers t)) ; to save again post-rename + (when (and buffer-file-name (denote-file-is-note-p buffer-file-name)) + (ignore-errors (denote-rename-file-using-front-matter buffer-file-name)) + (message "Buffer saved; Denote file renamed")))) + +(add-hook 'after-save-hook #'my-denote-always-rename-on-save-based-on-front-matter) +#+end_src + +** Narrow the list of files in Dired +:PROPERTIES: +:CUSTOM_ID: h:ea173a01-69ef-4574-89a7-6e60ede02f13 +:END: + +Emacs' standard file manager (or directory editor) can read a regular +expression to mark the matching files. This is the command +~dired-mark-files-regexp~, which is bound to =% m= by default. For +example, =% m _denote= will match all files that have the =denote= +keyword ([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). + +Once the files are matched, the user has two options: (i) narrow the +list to the matching items or (ii) exclude the matching items from the +list. + +For the former, we want to toggle the marks by typing =t= (calls the +command ~dired-toggle-marks~ by default) and then hit the letter =k= +(for ~dired-do-kill-lines~). The remaining files are those that match +the regexp that was provided earlier. + +For the latter approach of filtering out the matching items, simply +involves the use of the =k= command (~dired-do-kill-lines~) to omit the +marked files from the list. + +These sequences can be combined to incrementally narrow the list. Note +that ~dired-do-kill-lines~ does not delete files: it simply hides them +from the current view. + +Revert to the original listing with =g= (~revert-buffer~). + +For a convenient wrapper, consider this example: + +#+begin_src emacs-lisp +(defvar prot-dired--limit-hist '() + "Minibuffer history for `prot-dired-limit-regexp'.") + +;;;###autoload +(defun prot-dired-limit-regexp (regexp omit) + "Limit Dired to keep files matching REGEXP. + +With optional OMIT argument as a prefix (\\[universal-argument]), +exclude files matching REGEXP. + +Restore the buffer with \\`\\[revert-buffer]'." + (interactive + (list + (read-regexp + (concat "Files " + (when current-prefix-arg + (propertize "NOT " 'face 'warning)) + "matching PATTERN: ") + nil 'prot-dired--limit-hist) + current-prefix-arg)) + (dired-mark-files-regexp regexp) + (unless omit (dired-toggle-marks)) + (dired-do-kill-lines)) +#+end_src + +** Use ~dired-virtual-mode~ for arbitrary file listings +:PROPERTIES: +:CUSTOM_ID: h:d35d8d41-f51b-4139-af8f-9c8cc508e35b +:END: + +Emacs' Dired is a powerful file manager that builds its functionality +on top of the Unix =ls= command. As noted elsewhere in this manual, +the user can update the =ls= flags that Dired uses to display its +contents ([[#h:a7fd5e0a-78f7-434e-aa2e-e150479c16e2][I want to sort by last modified, why won't Denote let me?]]). + +What Dired cannot do is parse the output of a result that is produced +by piped commands, such as =ls -l | sort -t _ -k2=. This specific +example targets the second underscore-separated field of the file +name, per our conventions ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). Conceretely, it +matches the "alpha" as the sorting key in something like this: + +#+begin_src emacs-lisp +20220929T200432--testing-file-one__alpha.txt +#+end_src + +Consider then, how Dired will sort those files by their identifier: + +#+begin_src emacs-lisp +20220929T200432--testing-file-one__alpha.txt +20220929T200532--testing-file-two__beta.txt +20220929T200632--testing-file-three__alpha.txt +20220929T200732--testing-file-four__beta.txt +#+end_src + +Whereas on the command line, we can get the following: + +#+begin_example +$ ls | sort -t _ -k 2 +20220929T200432--testing-file-one__alpha.txt +20220929T200632--testing-file-three__alpha.txt +20220929T200532--testing-file-two__beta.txt +20220929T200732--testing-file-four__beta.txt +#+end_example + +This is where ~dired-virtual-mode~ shows its utility. If we tweak our +command-line invocation to include =ls -l=, this mode can behave like +Dired on the listed files. (We omit the output of the =-l= flag from +this tutorial, as it is too verbose.) + +What we now need is to capture the output of =ls -l | sort -t _ -k 2= +in an Emacs buffer and then enable ~dired-virtual-mode~. To do that, +we can rely on either =M-x shell= or =M-x eshell= and then manually +copy the relevant contents. + +For the user's convenience, I share what I have for Eshell to quickly +capture the last command's output in a dedicated buffer: + +#+begin_src emacs-lisp +(defcustom prot-eshell-output-buffer "*Exported Eshell output*" + "Name of buffer with the last output of Eshell command. +Used by `prot-eshell-export'." + :type 'string + :group 'prot-eshell) + +(defcustom prot-eshell-output-delimiter "* * *" + "Delimiter for successive `prot-eshell-export' outputs. +This is formatted internally to have newline characters before +and after it." + :type 'string + :group 'prot-eshell) + +(defun prot-eshell--command-prompt-output () + "Capture last command prompt and its output." + (let ((beg (save-excursion + (goto-char (eshell-beginning-of-input)) + (goto-char (point-at-bol))))) + (when (derived-mode-p 'eshell-mode) + (buffer-substring-no-properties beg (eshell-end-of-output))))) + +;;;###autoload +(defun prot-eshell-export () + "Produce a buffer with output of the last Eshell command. +If `prot-eshell-output-buffer' does not exist, create it. Else +append to it, while separating multiple outputs with +`prot-eshell-output-delimiter'." + (interactive) + (let ((eshell-output (prot-eshell--command-prompt-output))) + (with-current-buffer (get-buffer-create prot-eshell-output-buffer) + (let ((inhibit-read-only t)) + (goto-char (point-max)) + (unless (eq (point-min) (point-max)) + (insert (format "\n%s\n\n" prot-eshell-output-delimiter))) + (goto-char (point-at-bol)) + (insert eshell-output) + (switch-to-buffer-other-window (current-buffer)))))) +#+end_src + +Bind ~prot-eshell-export~ to a key in the ~eshell-mode-map~ and give +it a try (I use =C-c C-e=). In the produced buffer, activate the +~dired-virtual-mode~. + +** Use Embark to collect minibuffer candidates +:PROPERTIES: +:CUSTOM_ID: h:edf9b651-86eb-4d5f-bade-3c9e270082f0 +:END: + +=embark= is a remarkable package that lets you perform relevant, +context-dependent actions using a prefix key (simplifying in the +interest of brevity). + +For our purposes, Embark can be used to produce a Dired listing +directly from the minibuffer. Suppose the current note has links to +three other notes. You might use the ~denote-find-link~ command to +pick one via the minibuffer. But why not turn those three links into +their own Dired listing? While in the minibuffer, invoke ~embark-act~ +which you may have already bound to =C-.= and then follow it up with +=E= (for the ~embark-export~ command). + +This pattern can be repeated with any list of candidates, meaning that +you can narrow the list by providing some input before eventually +exporting the results with Embark. + +Overall, this is very powerful and you might prefer it over doing the +same thing directly in Dired, since you also benefit from all the power +of the minibuffer ([[#h:ea173a01-69ef-4574-89a7-6e60ede02f13][Narrow the list of files in Dired]]). + +** Search file contents +:PROPERTIES: +:CUSTOM_ID: h:76198fab-d6d2-4c67-9ccb-7a08cc883952 +:END: + +[ Users of ~consult~ can use the ~consult-denote~ package instead + ([[#h:113a13e1-35da-46f1-9414-81e9be2facc1][Use the ~consult-denote~ package for enhanced minibuffer interactions]]). ] + +Emacs provides built-in commands which are wrappers of standard Unix +tools: =M-x grep= lets the user input the flags of a ~grep~ call and +pass a regular expression to the =-e= flag. + +The author of Denote uses this thin wrapper instead: + +#+begin_src emacs-lisp +(defvar prot-search--grep-hist '() + "Input history of grep searches.") + +;;;###autoload +(defun prot-search-grep (regexp &optional recursive) + "Run grep for REGEXP. + +Search in the current directory using `lgrep'. With optional +prefix argument (\\[universal-argument]) for RECURSIVE, run a +search starting from the current directory with `rgrep'." + (interactive + (list + (read-from-minibuffer (concat (if current-prefix-arg + (propertize "Recursive" 'face 'warning) + "Local") + " grep for PATTERN: ") + nil nil nil 'prot-search--grep-hist) + current-prefix-arg)) + (unless grep-command + (grep-compute-defaults)) + (if recursive + (rgrep regexp "*" default-directory) + (lgrep regexp "*" default-directory))) +#+end_src + +Rather than maintain custom code, consider using the excellent =consult= +package: it provides commands such as ~consult-grep~ and ~consult-find~ +which provide live results and are generally easier to use than the +built-in commands. + +** Bookmark the directory with the notes +:PROPERTIES: +:CUSTOM_ID: h:1bba4c1e-6812-4749-948f-57df4fd49b36 +:END: + +Part of the reason Denote does not reinvent existing functionality is to +encourage you to learn more about Emacs. You do not need a bespoke +"jump to my notes" directory because such commands do not scale well. +Will you have a "jump to my downloads" then another for multimedia and +so on? No. + +Emacs has a built-in framework for recording persistent markers to +locations. Visit the ~denote-directory~ (or any dir/file for that +matter) and invoke the ~bookmark-set~ command (bound to =C-x r m= by +default). It lets you create a bookmark. + +The list of bookmarks can be reviewed with the ~bookmark-bmenu-list~ +command (bound to =C-x r l= by default). A minibuffer interface is +available with ~bookmark-jump~ (=C-x r b=). + +If you use the =consult= package, its default ~consult-buffer~ command +has the means to group together buffers, recent files, and bookmarks. +Each of those types can be narrowed to with a prefix key. The package +=consult-dir= is an extension to =consult= which provides useful extras +for working with directories, including bookmarks. + +** Treat your notes as a project +:PROPERTIES: +:CUSTOM_ID: h:fad3eb08-ddc7-43e4-ba28-210d89668037 +:END: + +Emacs has a built-in library for treating a directory tree as a +"project". This means that the contents of this tree are seen as part +of the same set, so commands like ~project-switch-to-buffer~ (=C-x p b= +by default) will only consider buffers in the current project +(e.g. three notes that are currently being visited). + +Normally, a "project" is a directory tree whose root is under version +control. For our purposes, all you need is to navigate to the +~denote-directory~ (for the shell or via Dired) and use the command-line +to run this (requires the =git= executable): + +: git init + +From Dired, you can type =M-!= which invokes ~dired-smart-shell-command~ +and then run the git call there. + +The project can then be registered by invoking any project-related +command inside of it, such as ~project-find-file~ (=C-x p f=). + +It is a good idea to keep your notes under version control, as that +gives you a history of changes for each file. We shall not delve into +the technicalities here, though suffice to note that Emacs' built-in +version control framework or the exceptionally well-crafted =magit= +package will get the job done (VC can work with other backends besides +Git). + +** Use the tree-based file prompt for select commands +:PROPERTIES: +:CUSTOM_ID: h:8f9e0971-8b30-4e7b-af79-8fed257dbcfa +:END: + +Older versions of Denote had a file prompt that resembled that of the +standard ~find-file~ command (bound to =C-x C-f= by default). This +means that it used a tree-based method of navigating the filesystem by +selecting the specific directory and then the given file. + +Currently, Denote flattens the file prompt so that every file in the +~denote-directory~ and its subdirectories can be matched from anywhere +using the power of Emacs' minibuffer completion (such as with the help +of the ~orderless~ package in addition to built-in options). + +Users who need the old behaviour on a per-command basis can define +their own wrapper functions as shown in the following code block. + +#+begin_src emacs-lisp +;; This is the old `denote-file-prompt' that we renamed to +;; `denote-file-prompt-original' for clarity. +(defun denote-file-prompt-original (&optional initial-text) + "Prompt for file with identifier in variable `denote-directory'. +With optional INITIAL-TEXT, use it to prepopulate the minibuffer." + (read-file-name "Select note: " (denote-directory) nil nil initial-text + (lambda (f) + (or (denote-file-has-identifier-p f) + (file-directory-p f))))) + +;; Our wrapper command that changes the current `denote-file-prompt' +;; to the functionality of `denote-file-prompt-original' only when +;; this command is used. +(defun my-denote-link () + "Call `denote-link' but use Denote's original file prompt. +See `denote-file-prompt-original'." + (interactive) + (cl-letf (((symbol-function 'denote-file-prompt) #'denote-file-prompt-original)) + (call-interactively #'denote-link))) +#+end_src + +** Rename files with Denote in the Image Dired thumbnails buffer +:PROPERTIES: +:CUSTOM_ID: h:e666ced6-da75-4bdb-9be3-82c2f4455ee9 +:END: + +[[#h:2d5ee9bf-e8f2-426c-8bf7-bf78bc88d1ee][Rename files with Denote using ~dired-preview~]] + +Just as with the ~denote-dired-rename-marked-files-with-keywords~, +we can use Denote in the Image Dired buffer ([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files at once]]). +Here is the custom code: + +#+begin_src emacs-lisp +(autoload 'image-dired--with-marked "image-dired") +(autoload 'image-dired-original-file-name "image-dired-util") + +(defun my-denote-image-dired-rename-marked-files (keywords) + "Like `denote-dired-rename-marked-files-with-keywords' but for Image Dired. +Prompt for KEYWORDS and rename all marked files in the Image +Dired buffer to have a Denote-style file name with the given +KEYWORDS. + +IMPORTANT NOTE: if there are marked files in the corresponding +Dired buffers, those will be targeted as well. This is not the +fault of Denote: it is how Dired and Image Dired work in tandem. +To only rename the marked thumbnails, start by unmarking +everything in Dired. Then mark the items in Image Dired and +invoke this command." + (interactive (list (denote-keywords-prompt)) image-dired-thumbnail-mode) + (image-dired--with-marked + (when-let* ((file (image-dired-original-file-name)) + (dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) "")) + (file-type (denote-filetype-heuristics file)) + (title (denote--retrieve-title-or-filename file file-type)) + (signature (or (denote-retrieve-filename-signature file) "") + (extension (file-name-extension file t)) + (new-name (denote-format-file-name dir id keywords title extension signature)) + (default-directory dir)) + (denote-rename-file-and-buffer file new-name)))) +#+end_src + +While the ~my-denote-image-dired-rename-marked-files~ renames files in +the helpful Denote-compliant way, users may still need to not prepend +a unique identifier and not sluggify (hyphenate and downcase) the +image's existing file name. To this end, the following custom command +can be used instead: + +#+begin_src emacs-lisp +(defun my-image-dired-rename-marked-files (keywords) + "Like `denote-dired-rename-marked-files-with-keywords' but for Image Dired. +Prompt for keywords and rename all marked files in the Image +Dired buffer to have Denote-style keywords, but none of the other +conventions of Denote's file-naming scheme." + (interactive (list (denote-keywords-prompt)) image-dired-thumbnail-mode) + (image-dired--with-marked + (when-let* ((file (image-dired-original-file-name)) + (dir (file-name-directory file)) + (file-type (denote-filetype-heuristics file)) + (title (denote--retrieve-title-or-filename file file-type)) + (extension (file-name-extension file t)) + (kws (denote--keywords-combine keywords)) + (new-name (concat dir title "__" kws extension)) + (default-directory dir)) + (denote-rename-file-and-buffer file new-name)))) +#+end_src + +** Rename files with Denote using ~dired-preview~ +:PROPERTIES: +:CUSTOM_ID: h:2d5ee9bf-e8f2-426c-8bf7-bf78bc88d1ee +:END: + +The ~dired-preview~ package (by me/Protesilaos) automatically displays +a preview of the file at point in Dired. This can be helpful in +tandem with Denote when we want to rename multiple files by taking a +quick look at their contents. + +The command ~denote-dired-rename-marked-files-with-keywords~ +will generate Denote-style file names based on the keywords it prompts +for. Identifiers are derived from each file's modification date +([[#h:1b6b2c78-42f0-45b8-9ef0-6de21a8b2cde][Rename multiple files at once]]). There is no need for any custom code +in this scenario. + +As noted in the section about Image Dired, the user may sometimes not +need a fully fledged Denote-style file name but only append Denote-like +keywords to each file name (e.g. =Original Name__denote_test.jpg= +instead of =20230710T195843--original-name__denote_test.jpg=). + +[[#h:e666ced6-da75-4bdb-9be3-82c2f4455ee9][Rename files with Denote in the Image Dired thumbnails buffer]] + +In such a workflow, it is unlikely to be dealing with ordinary text +files where front matter can be helpful. A custom command does not +need to behave like what Denote provides out-of-the-box, but can +instead append keywords to file names without conducting any further +actions. We thus have: + +#+begin_src emacs-lisp +(defun my-denote-dired-rename-marked-files-keywords-only () + "Like `denote-dired-rename-marked-files-with-keywords' but only for keywords in file names. + +Prompt for keywords and rename all marked files in the Dired +buffer to only have Denote-style keywords, but none of the other +conventions of Denote's file-naming scheme." + (interactive nil dired-mode) + (if-let* ((marks (dired-get-marked-files))) + (let ((keywords (denote-keywords-prompt))) + (dolist (file marks) + (let* ((dir (file-name-directory file)) + (file-type (denote-filetype-heuristics file)) + (title (denote--retrieve-title-or-filename file file-type)) + (extension (file-name-extension file t)) + (kws (denote--keywords-combine keywords)) + (new-name (concat dir title "__" kws extension))) + (denote-rename-file-and-buffer file new-name))) + (revert-buffer)) + (user-error "No marked files; aborting"))) +#+end_src + +** Avoid duplicate identifiers when exporting Denote notes +:PROPERTIES: +:CUSTOM_ID: h:4a8c4546-26b3-4195-8b2c-b08a519986a4 +:END: + +When exporting Denote notes to, for example, an HTML or PDF file, +there is a high probability that the same file name is used with a new +extension. This is problematic because it creates files with +duplicate identifiers. The =20230515T085612--example__keyword.org= +produces a =20230515T085612--example__keyword.pdf=. Any link to the +=20230515T085612= will thus break: it does not honor Denote's +expectation of finding unique identifiers. This is not the fault of +Denote: exporting is done by the user without Denote's involvement. + +Org Mode and Markdown use different approaches to exporting files. No +recommended method is available for plain text files as there is no +standardised export functionality for this format (the user can always +create a new note using the file type they want on a case-by-case +basis: [[#h:887bdced-9686-4e80-906f-789e407f2e8f][Convenience commands for note creation]]). + +*** Export Denote notes with Org Mode +:PROPERTIES: +:CUSTOM_ID: h:67669d9d-17c3-45bd-8227-da57d8bc3b73 +:END: + +Org Mode has a built-in configurable export engine. You can prevent +duplicate identifiers when exporting manually for each exported file +or by advising the Org export function. + +The ~denote-org~ package (by Protesilaos) also provides commands to +convert =denote:= links to their =file:= equivalent, in case this is a +required pre-processing step for export purposes. + +**** Manually configure Org export +:PROPERTIES: +:CUSTOM_ID: h:bf791e28-73e5-4ed8-88bc-e4e9b3ebaedb +:END: + +Insert =#+export_file_name: FILENAME= in the front matter before +exporting to force a filename called whatever the value of =FILENAME= +is. The =FILENAME= does not specify the file type extension, such as +=.pdf=. This is up to the export engine. For example, a Denote note +with a complete file name of =20230515T085612--example__keyword.org= +and a front matter entry of =#+export_file_name: hello= will be +exported as =hello.pdf=. + +The advantage of this manual method is that it gives the user full +control over the resulting file name. The disadvantage is that it +depends on the user's behaviour. Forgetting to add a new name can +lead to duplicate identifiers, as already noted in the introduction to +this section ([[#h:4a8c4546-26b3-4195-8b2c-b08a519986a4][Export Denote notes]]). + +**** Automatically store Org exports in another folder +:PROPERTIES: +:CUSTOM_ID: h:7a61a370-78e5-42a1-9650-98fee140723f +:END: + +It is possible to automatically place all exports in another folder by +making Org's function ~org-export-output-file-name~ create the target +directory if needed and move the exported file there. Remember that +advising Elisp code must be handled with care, as it might break the +original function in subtle ways. + +#+begin_src emacs-lisp +(defvar my-org-export-output-directory-prefix "./export_" + "Prefix of directory used for org-mode export. + +The single dot means that the directory is created on the same +level as the one where the Org file that performs the exporting +is. Use two dots to place the directory on a level above the +current one. + +If this directory is part of `denote-directory', make sure it is +not read by Denote. See `denote-excluded-directories-regexp'. +This way there will be no known duplicate Denote identifiers +produced by the Org export mechanism.") + +(defun my-org-export-create-directory (fn extension &rest args) + "Move Org export file to its appropriate directory. + +Append the file type EXTENSION of the exported file to +`my-org-export-output-directory-prefix' and, if absent, create a +directory named accordingly. + +Install this as advice around `org-export-output-file-name'. The +EXTENSION is supplied by that function. ARGS are its remaining +arguments." + (let ((export-dir (format "%s%s" my-org-export-output-directory-prefix extension))) + (unless (file-directory-p export-dir) + (make-directory export-dir))) + (apply fn extension args)) + +(advice-add #'org-export-output-file-name :around #'my-org-export-create-directory) +#+end_src + +The target export directory should not be a subdirectory of +~denote-directory~, as that will result in duplicate identifiers. +Exclude it with the ~denote-excluded-directories-regexp~ user option +([[#h:8458f716-f9c2-4888-824b-2bf01cc5850a][Exclude certain directories from all operations]]). + +[ NOTE: I (Protesilaos) am not a LaTeX user and cannot test the + following. ] + +Using a different directory will require some additional configuration +when exporting using LaTeX. The export folder cannot be inside the +path of the ~denote-directory~ to prevent Denote from recognising it +as an attachment: +. + +**** Org Mode Publishing +:PROPERTIES: +:CUSTOM_ID: h:2f3451ed-2fc4-4f36-bcf2-112939963e20 +:END: + +Org Mode also has a publishing tool for exporting a collection of +files. Some user might apply this approach to convert their note +collection to a public or private website. + +The ~org-publish-project-alist~ variable drives the publishing +process, including the publishing directory. + +The publishing directory should not be a subdirectory of +~denote-directory~, as that will result in duplicate identifiers. +Exclude it with the ~denote-excluded-directories-regexp~ user option +([[#h:8458f716-f9c2-4888-824b-2bf01cc5850a][Exclude certain directories from all operations]]). + +*** Export Denote notes with Markdown +:PROPERTIES: +:CUSTOM_ID: h:44c6a34a-e9ad-4f43-a24f-12f2c5a8467e +:END: + +Exporting from Markdown requires an external processor (e.g., +Markdown.pl, Pandoc, or MultiMarkdown). The ~markdown-command~ +variable defines the command line used in export, for example: + +#+begin_src emacs-lisp +(setq markdown-command "multimarkdown") +#+end_src + +The export process thus occurs outside of Emacs. Users need to read +the documentation of their preferred processor to prevent the creation +of duplicate Denote identifiers. + +** Set up your workflow for daily or weekly meeting notes +:PROPERTIES: +:CUSTOM_ID: h:00998f94-0194-43ef-b349-260106ef7177 +:END: + +Perhaps as part of work, we meet with certain people on a regular +basis. During the meeting we may discuss a variety of topics. How best +to approach with the help of Denote? + +One option is to write a new file for each meeting, giving it the +appropriate keywords each time ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). This is what Denote +does by default and does not need any further tweaks. If we need to +review those notes, we can use the command ~denote-sort-dired~ +([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Sort files by component]]), or one of the Org dynamic blocks we provide +([[#h:8b542c50-dcc9-4bca-8037-a36599b22779][Use Org dynamic blocks]]), among other options. + +Another approach is to write one file per person with the regular +~denote~ command (or related), give it the name of the person as a +title and, optionally, use some relevant keywords. Inside each file, +write a top-level heading with the date of the meeting, and then +produce the meeting notes below as paragraphs and subheadings. This +can all be done without any changes to Denote, though we can +streamline it by incorporating the following code in our setup. +Configure ~my-denote-colleagues~ and then use the command +~my-denote-colleagues-new-meeting~ to see how it works. + +#+begin_src emacs-lisp +(defvar my-denote-colleagues '("Prot" "Protesilaos") + "List of names I collaborate with. +There is at least one file in the variable `denote-directory' that has +the name of this person.") + +(defvar my-denote-colleagues-prompt-history nil + "Minibuffer history for `my-denote-colleagues-new-meeting'.") + +(defun my-denote-colleagues-prompt () + "Prompt with completion for a name among `my-denote-colleagues'. +Use the last input as the default value." + (let ((default-value (car my-denote-colleagues-prompt-history))) + (completing-read + (format-prompt "New meeting with COLLEAGUE" default-value) + my-denote-colleagues + nil :require-match nil + 'my-denote-colleagues-prompt-history + default-value))) + +(defun my-denote-colleagues-get-file (name) + "Find file in variable `denote-directory' for NAME colleague. +If there are more than one files, prompt with completion for one among +them. + +NAME is one among `my-denote-colleagues'." + (if-let* ((files (denote-directory-files name)) + (length-of-files (length files))) + (cond + ((= length-of-files 1) + (car files)) + ((> length-of-files 1) + (completing-read "Select a file: " files nil :require-match))) + (user-error "No files for colleague with name `%s'" name))) + +(defun my-denote-colleagues-new-meeting () + "Prompt for the name of a colleague and insert a timestamped heading therein. +The name of a colleague corresponds to at least one file in the variable +`denote-directory'. In case there are multiple files, prompt to choose +one among them and operate therein." + (declare (interactive-only t)) + (interactive) + (let* ((name (my-denote-colleagues-prompt)) + (file (my-denote-colleagues-get-file name)) + (time (format-time-string "%F %a %R"))) ; remove %R if you do not want the time + (with-current-buffer (find-file file) + (goto-char (point-max)) + ;; Here I am assuming we are in `org-mode', hence the leading + ;; asterisk for the heading. Adapt accordingly. + (insert (format "* [%s]\n\n" time))))) +#+end_src + +* For developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:c916d8c5-540a-409f-b780-6ccbd90e088e +:END: + +Denote is in a stable state and can be relied upon as the basis for +custom extensions ([[#h:08b14682-e73f-4b11-b2e2-be3b788c8572][Packages that build on Denote]]). Further below is a +list with the functions or variables we provide for public usage. +Those are in addition to all user options and commands that are +already documented in the various sections of this manual. + +In this context "public" is any form with single hyphens in its symbol, +such as ~denote-directory-files~. We expressly support those, meaning +that we consider them reliable and commit to documenting any changes in +their particularities (such as through ~make-obsolete~, a record in the +change log, a blog post on the maintainer's website, and the like). + +By contradistinction, a "private" form is declared with two hyphens in +its symbol such as ~denote--file-extension~. Do not use those as we +might change them without further notice. + +The following sections cover the specifics. + +** Common building blocks for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:c98aed45-341a-40a0-91ce-347a29c98ab4 +:END: + +#+vindex: denote-id-format ++ Variable ~denote-id-format~ :: Format of ID prefix of a note's + filename. The note's ID is derived from the date and time of its + creation ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+vindex: denote-id-regexp ++ Variable ~denote-id-regexp~ :: Regular expression to match + ~denote-id-format~. + +#+vindex: denote-signature-regexp ++ Variable ~denote-signature-regexp~ :: Regular expression to match + the =SIGNATURE= field in a file name. + +#+vindex: denote-title-regexp ++ Variable ~denote-title-regexp~ :: Regular expression to match the + =TITLE= field in a file name ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+vindex: denote-keywords-regexp ++ Variable ~denote-keywords-regexp~ :: Regular expression to match the + =KEYWORDS= field in a file name ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+findex: denote-identifier-p ++ Function ~denote-identifier-p~ :: Return non-nil if =IDENTIFIER= + string is a Denote identifier. + +#+findex: denote-file-is-note-p ++ Function ~denote-file-is-note-p~ :: Return non-nil if =FILE= is an + actual Denote note. For our purposes, a note must satisfy + ~file-regular-p~ and ~denote-filename-is-note-p~. + +#+findex: denote-file-has-identifier-p ++ Function ~denote-file-has-identifier-p~ :: Return non-nil if =FILE= + has a Denote identifier. + +#+findex: denote-file-has-denoted-filename-p ++ Function ~denote-file-has-denoted-filename-p~ :: Return non-nil if + =FILE= respects the file-naming scheme of Denote. This tests the + rules of Denote's file-naming scheme. Sluggification is ignored. It + is done by removing all file name components and validating what + remains. + +#+findex: denote-file-has-signature-p ++ Function ~denote-file-has-signature-p~ :: Return non-nil if =FILE= + has a signature. + +#+findex: denote-file-has-supported-extension-p ++ Function ~denote-file-has-supported-extension-p~ :: Return non-nil + if =FILE= has supported extension. Also account for the possibility + of an added =.gpg= suffix. Supported extensions are those implied by + ~denote-file-type~. + +#+findex: denote-file-is-writable-and-supported-p ++ Function ~denote-file-is-writable-and-supported-p~ :: Return non-nil + if =FILE= is writable and has supported extension. + +#+findex: denote-file-type-extensions ++ Function ~denote-file-type-extensions~ :: Return all file type + extensions in ~denote-file-types~. + +#+vindex: denote-encryption-file-extensions ++ Variable ~denote-encryption-file-extensions~ :: List of strings + specifying file extensions for encryption. + +#+findex: denote-file-type-extensions-with-encryption ++ Function ~denote-file-type-extensions-with-encryption~ :: Derive + ~denote-file-type-extensions~ plus ~denote-encryption-file-extensions~. + +#+findex: denote-get-file-extension ++ Function ~denote-get-file-extension~ :: Return extension of =FILE= + with dot included. Account for ~denote-encryption-file-extensions~. + In other words, return something like =.org.gpg= if it is part of + the file, else return =.org=. + +#+findex: denote-get-file-extension-sans-encryption ++ Function ~denote-get-file-extension-sans-encryption~ :: Return + extension of =FILE= with dot included and without the encryption + part. Build on top of ~denote-get-file-extension~ though always + return something like =.org= even if the actual file extension is + =.org.gpg=. + +#+findex: denote-infer-keywords-from-files ++ Functions ~denote-infer-keywords-from-files~ :: Return list of + keywords in ~denote-directory-files~. With optional + =FILES-MATCHING-REGEXP=, only extract keywords from the matching + files. Otherwise, do it for all files. Keep any duplicates. Users + who do not want duplicates should refer to the functions + ~denote-keywords~. + +#+findex: denote-keywords ++ Function ~denote-keywords~ :: Return appropriate list of keyword + candidates. If ~denote-infer-keywords~ is non-nil, infer keywords + from existing notes and combine them into a list with + ~denote-known-keywords~. Else use only the latter set of keywords + ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). In the case of keyword inferrence, use + optional =FILES-MATCHING-REGEXP=, to extract keywords only from the + matching files. Otherwise, do it for all files. Filter inferred + keywords with the user option ~denote-excluded-keywords-regexp~. + +#+findex: denote-keywords-sort ++ Function ~denote-keywords-sort~ :: Sort =KEYWORDS= if + ~denote-sort-keywords~ is non-nil. =KEYWORDS= is a list of strings, + per ~denote-keywords-prompt~. + +#+findex: denote-keywords-combine ++ Function ~denote-keywords-combine~ :: Combine =KEYWORDS= list of + strings into a single string. Keywords are separated by the + underscore character, per the Denote file-naming scheme. + +#+findex: denote-valid-date-p ++ Function ~denote-valid-date-p~ :: Return =DATE= as a valid date. A + valid =DATE= is a value that can be parsed by either ~decode-time~ + or ~date-to-time~ .Those functions signal an error if =DATE= is a + value they do not recognise. If =DATE= is nil, return nil. + +#+findex: denote-directory ++ Function ~denote-directory~ :: Return path of the variable + ~denote-directory~ as a proper directory, also because it accepts a + directory-local value for what we internally refer to as "silos" + ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directories for notes]]). Custom Lisp code can + ~let~ bind the value of the variable ~denote-directory~ + to override what this function returns. + +#+findex: denote-directory-files ++ Function ~denote-directory-files~ :: Return list of absolute file + paths in variable ~denote-directory~. Files that match + ~denote-excluded-files-regexp~ are excluded from the list. Files + only need to have an identifier. The return value may thus include + file types that are not implied by ~denote-file-type~. With optional + =FILES-MATCHING-REGEXP=, restrict files to those matching the given + regular expression. With optional =OMIT-CURRENT= as a non-nil value, + do not include the current Denote file in the returned list. With + optional =TEXT-ONLY= as a non-nil value, limit the results to text + files that satisfy ~denote-file-is-note-p~. With optional + =EXCLUDE-REGEXP= exclude the files that match the given regular + expression. This is done after =FILES-MATCHING-REGEXP= and + =OMIT-CURRENT= have been applied. + +#+findex: denote-directory-subdirectories ++ Function ~denote-directory-subdirectories~ :: Return list of + subdirectories in variable ~denote-directory~. Omit dotfiles (such + as .git) unconditionally. Also exclude whatever matches + ~denote-excluded-directories-regexp~. Note that the + ~denote-directory~ accepts a directory-local value for what we call + "silos" ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directories for notes]]). + +** File path interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:08e696e4-c9e1-48b4-8349-e53451aed1df +:END: + +#+findex: denote-file-name-relative-to-denote-directory ++ Function ~denote-file-name-relative-to-denote-directory~ :: Return + name of =FILE= relative to the variable ~denote-directory~. =FILE= + must be an absolute path. + +#+findex: denote-slug-keep-only-ascii ++ Function ~denote-slug-keep-only-ascii~ :: Remove all non-ASCII + characters from =STR= and replace them with spaces. This is useful + as a helper function to construct ~denote-file-name-slug-functions~ + ([[#h:d1e4eb5b-e7f2-4a3b-9243-e1c653817a4a][Custom sluggification to remove non-ASCII characters]]). + +#+findex: denote-sluggify ++ Function ~denote-sluggify~ :: Make =STR= an appropriate slug for + file name =COMPONENT= ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). + Apply the function specified in ~denote-file-name-slug-function~ to + =COMPONENT= which is one of =title=, =signature=, =keyword=. If the + resulting string still contains consecutive =-=,=_= or ~=~, they are + replaced by a single occurence of the character, if necessary + according to =COMPONENT=. If =COMPONENT= is ~keyword~, remove + underscores from =STR= as they are used as the keywords separator in + file names. + +#+findex: denote-sluggify-keyword ++ Function ~denote-sluggify-keyword~ :: Sluggify =STR= while joining + separate words. + +#+findex: denote-sluggify-signature ++ Function ~denote-sluggify-signature~ :: Make =STR= an appropriate + slug for signatures ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). + +#+findex: denote-sluggify-keywords ++ Function ~denote-sluggify-keywords~ :: Sluggify =KEYWORDS=, which is + a list of strings ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). + +#+findex: denote-use-date ++ Function ~denote-use-date~ :: The date to be used in a note creation + command. See the documentation of ~denote~ for acceptable values. + This variable is ignored if nil. Only ever ~let~ bind this, + otherwise the title will always be the same and the title prompt + will be skipped. + +#+findex: denote-use-directory ++ Function ~denote-use-directory~ :: The directory to be used in a + note creation command. See the documentation of ~denote~ for + acceptable values. This variable is ignored if nil. Only ever ~let~ + bind this, otherwise the title will always be the same and the title + prompt will be skipped. + +#+findex: denote-use-file-type ++ Function ~denote-use-file-type~ :: The file type to be used in a + note creation command. See the documentation of ~denote~ for + acceptable values. This variable is ignored if nil. Only ever ~let~ + bind this, otherwise the title will always be the same and the title + prompt will be skipped. + +#+findex: denote-use-keywords ++ Function ~denote-use-keywords~ :: The keywords to be used in a note + creation command. See the documentation of ~denote~ for acceptable + values. This variable is ignored if ~default~. Only ever ~let~ bind this, + otherwise the title will always be the same and the title prompt + will be skipped. + +#+findex: denote-use-signature ++ Function ~denote-use-signature~ :: The signature to be used in a + note creation command. See the documentation of ~denote~ for + acceptable values. This variable is ignored if nil. Only ever ~let~ + bind this, otherwise the title will always be the same and the title + prompt will be skipped. + +#+findex: denote-use-template ++ Function ~denote-use-template~ :: The template to be used in a note + creation command. See the documentation of ~denote~ for acceptable + values. This variable is ignored if nil. Only ever ~let~ bind this, + otherwise the title will always be the same and the title prompt + will be skipped. + +#+findex: denote-use-title ++ Function ~denote-use-title~ :: The title to be used in a note + creation command. See the documentation of ~denote~ for acceptable + values. This variable is ignored if nil. Only ever ~let~ bind this, + otherwise the title will always be the same and the title prompt + will be skipped. + +#+findex: denote-format-file-name ++ Function ~denote-format-file-name~ :: Format file name. =DIR-PATH=, + =ID=, =KEYWORDS=, =TITLE=, =EXTENSION= and =SIGNATURE= are expected to + be supplied by ~denote~ or equivalent command. + + =DIR-PATH= is a string pointing to a directory. It ends with a + forward slash (the function ~denote-directory~ makes sure this is + the case when returning the value of the variable ~denote-directory~). + =DIR-PATH= cannot be nil or an empty string. + + =ID= is a string holding the identifier of the note. It can be an + empty string, in which case its respective file name component is + not added to the base file name. + + =DIR-PATH= and =ID= form the base file name. + + =KEYWORDS= is a list of strings that is reduced to a single string + by ~denote-keywords-combine~. =KEYWORDS= can be an empty list or a + nil value, in which case the relevant file name component is not + added to the base file name. + + =TITLE= and =SIGNATURE= are strings. They can be an empty string, in + which case their respective file name component is not added to the + base file name. + + =EXTENSION= is a string that contains a dot followed by the file + type extension. It can be an empty string or a nil value, in which + case it is not added to the base file name. + +** Data retrieval interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:9b32bf7d-1569-4495-801c-8835f022d72e +:END: + +#+findex: denote-get-path-by-id ++ Function ~denote-get-path-by-id~ :: Return absolute path of =ID= + string in ~denote-directory-files~. + +#+findex: denote-get-identifier-at-point ++ Function ~denote-get-identifier-at-point~ :: Return the identifier + at point or =POINT=. + +#+findex: denote-extract-keywords-from-path ++ Function ~denote-extract-keywords-from-path~ :: Extract keywords + from =PATH= and return them as a list of strings. =PATH= must be a + Denote-style file name where keywords are prefixed with an + underscore. If =PATH= has no such keywords, which is possible, + return nil ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+findex: denote-extract-id-from-string ++ Function ~denote-extract-id-from-string~ :: Return existing Denote + identifier in =STRING=, else nil. + +#+findex: denote-retrieve-filename-identifier ++ Function ~denote-retrieve-filename-identifier~ :: Extract identifier + from =FILE= name, if present, else return nil. To create a new one + from a date, refer to the ~denote-get-identifier~ function. + +#+findex: denote-retrieve-filename-title ++ Function ~denote-retrieve-filename-title~ :: Extract Denote title + component from =FILE= name, if present, else return nil. + +#+findex: denote-retrieve-filename-keywords ++ Function ~denote-retrieve-filename-keywords~ :: Extract keywords + from =FILE= name, if present, else return nil. Return + matched keywords as a single string. + +#+findex: denote-retrieve-filename-signature ++ Function ~denote-retrieve-filename-signature~ :: Extract signature + from =FILE= name, if present, else return nil. + +#+findex: denote-retrieve-title-or-filename ++ Function ~denote-retrieve-title-or-filename~ :: Return appropriate + title for =FILE= given its =TYPE=. This is a wrapper for + ~denote-retrieve-front-matter-title-value~ and + =denote-retrieve-filename-title=. + +#+findex: denote-get-identifier ++ Function ~denote-get-identifier~ :: Convert =DATE= into a Denote + identifier using ~denote-id-format~. If =DATE= is nil, return an + empty string as the identifier. + +#+findex: denote-retrieve-front-matter-title-value ++ Function ~denote-retrieve-front-matter-title-value~ :: Return title value from + =FILE= front matter per =FILE-TYPE=. + +#+findex: denote-retrieve-front-matter-title-line ++ Function ~denote-retrieve-front-matter-title-line~ :: Return title line from + =FILE= front matter per =FILE-TYPE=. + +#+findex: denote-retrieve-front-matter-keywords-value ++ Function ~denote-retrieve-front-matter-keywords-value~ :: Return keywords value + from =FILE= front matter per =FILE-TYPE=. The return value is a list + of strings. + +#+findex: denote-retrieve-front-matter-keywords-line ++ Function ~denote-retrieve-front-matter-keywords-line~ :: Return keywords line + from =FILE= front matter per =FILE-TYPE=. + +** Prompt interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:e9d05541-bd00-43b9-8146-8c4445f78f07 +:END: + +#+findex: denote-add-prompts ++ Function ~denote-add-prompts~ :: Add list of =ADDITIONAL-PROMPTS= to + ~denote-prompts~. This is best done inside of a ~let~ to create a + wrapper function around ~denote~, ~denote-rename-file~, and + generally any command that consults the value of ~denote-prompts~. + +#+findex: denote-signature-prompt ++ Function ~denote-signature-prompt~ :: Prompt for signature string. + With optional =INITIAL-SIGNATURE= use it as the initial minibuffer + text. With optional =PROMPT-TEXT= use it in the minibuffer instead + of the default prompt. Previous inputs at this prompt are available + for minibuffer completion if the user option ~denote-history-completion-in-prompts~ + is set to a non-nil value ([[#h:403422a7-7578-494b-8f33-813874c12da3][The ~denote-history-completion-in-prompts~ option]]). + +#+findex: denote-file-prompt ++ Function ~denote-file-prompt~ :: Prompt for file in variable + ~denote-directory~. Files that match ~denote-excluded-files-regexp~ + are excluded from the list. With optional =FILES-MATCHING-REGEXP=, + filter the candidates per the given regular expression. With + optional =PROMPT-TEXT=, use it instead of the default call to select + a file. With optional =NO-REQUIRE-MATCH= accept the given input + as-is. Return the absolute path to the matching file. + +#+findex: denote-keywords-prompt ++ Function ~denote-keywords-prompt~ :: Prompt for one or more keywords. + Read entries as separate when they are demarcated by the + ~crm-separator~, which typically is a comma. With optional + =PROMPT-TEXT=, use it to prompt the user for keywords. Else use a + generic prompt. With optional =INITIAL-KEYWORDS= use them as the + initial minibuffer text. + +#+findex: denote-title-prompt ++ Function ~denote-title-prompt~ :: Prompt for title string. With + optional =INITIAL-TITLE= use it as the initial minibuffer text. With + optional =PROMPT-TEXT= use it in the minibuffer instead of the + default prompt. Previous inputs at this prompt are available + for minibuffer completion if the user option ~denote-history-completion-in-prompts~ + is set to a non-nil value ([[#h:403422a7-7578-494b-8f33-813874c12da3][The ~denote-history-completion-in-prompts~ option]]). + +#+vindex: denote-title-prompt-current-default ++ Variable ~denote-title-prompt-current-default~ :: Currently bound + default title for ~denote-title-prompt~. Set the value of this + variable within the lexical scope of a command that needs to supply + a default title before calling ~denote-title-prompt~ and use + ~unwind-protect~ to set its value back to nil. + +#+findex: denote-file-type-prompt ++ Function ~denote-file-type-prompt~ :: Prompt for ~denote-file-type~. + Note that a non-nil value other than ~text~, ~markdown-yaml~, and + ~markdown-toml~ falls back to an Org file type. We use ~org~ here + for clarity. + +#+findex: denote-date-prompt ++ Function ~denote-date-prompt~ :: Prompt for date, expecting + =YYYY-MM-DD= or that plus =HH:MM= (or even =HH:MM:SS=). Use Org's + more advanced date selection utility if the user option + ~denote-date-prompt-use-org-read-date~ is non-nil. It requires Org + ([[#h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63][The denote-date-prompt-use-org-read-date option]]). With optional + =INITIAL-DATE= use it as the initial minibuffer text. With optional + =PROMPT-TEXT= use it in the minibuffer instead of the default + prompt. =INITIAL-DATE= is a string that can be processed by + ~denote-valid-date-p~, a value that can be parsed by ~decode-time~ + or nil. + +#+findex: denote-command-prompt ++ Function ~denote-command-prompt~ :: Prompt for command among + ~denote-commands-for-new-notes~ ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). + +#+vindex: denote-prompts-with-history-as-completion ++ Variable ~denote-prompts-with-history-as-completion~ :: Prompts that + conditionally perform completion against their history. These are + minibuffer prompts that ordinarily accept a free form string input, + as opposed to matching against a predefined set. These prompts can + optionally perform completion against their own minibuffer history + when the user option ~denote-history-completion-in-prompts~ is set + to a non-nil value ([[#h:403422a7-7578-494b-8f33-813874c12da3][The ~denote-history-completion-in-prompts~ option]]). + +#+findex: denote-files-matching-regexp-prompt ++ Function ~denote-files-matching-regexp-prompt~ :: Prompt for + =REGEXP= to filter Denote files by. With optional =PROMPT-TEXT= use + it instead of a generic prompt. + +#+findex: denote-prompt-for-date-return-id ++ Function ~denote-prompt-for-date-return-id~ :: Use + ~denote-date-prompt~ and return it as ~denote-id-format~. + +#+findex: denote-template-prompt ++ Function ~denote-template-prompt~ :: Prompt for template key in + ~denote-templates~ and return its value. + +#+findex: denote-subdirectory-prompt ++ Function ~denote-subdirectory-prompt~ :: Prompt for subdirectory of + the variable ~denote-directory~. The table uses the ~file~ + completion category (so it works with packages such as ~marginalia~ + and ~embark~). + +** Front matter interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:80ddc044-5c64-4fb3-b4f4-2eaf6bceda3b +:END: + +#+findex: denote-filetype-heuristics ++ Function ~denote-filetype-heuristics~ :: Return likely file type of + =FILE=. If in the process of ~org-capture~, consider the file type to + be that of Org. Otherwise, use the file extension to detect the file + type of =FILE=. + + If more than one file type correspond to this file extension, use + the first file type for which the :title-key-regexp in + ~denote-file-types~ matches in the file. + + Return nil if the file type is not recognized. + +#+vindex: denote-org-front-matter ++ Variable ~denote-org-front-matter~ :: Specifies the Org front + matter. It is passed to ~format~ with arguments =TITLE=, =DATE=, + =KEYWORDS=, =ID= ([[#h:7f918854-5ed4-4139-821f-8ee9ba06ad15][Change the front matter format]]) + +#+vindex: denote-yaml-front-matter ++ Variable ~denote-yaml-front-matter~ :: Specifies the YAML (Markdown) + front matter. It is passed to ~format~ with arguments =TITLE=, + =DATE=, =KEYWORDS=, =ID= ([[#h:7f918854-5ed4-4139-821f-8ee9ba06ad15][Change the front matter format]]) + +#+vindex: denote-toml-front-matter ++ Variable ~denote-toml-front-matter~ :: Specifies the TOML (Markdown) + front matter. It is passed to ~format~ with arguments =TITLE=, + =DATE=, =KEYWORDS=, =ID= ([[#h:7f918854-5ed4-4139-821f-8ee9ba06ad15][Change the front matter format]]) + +#+vindex: denote-text-front-matter ++ Variable ~denote-text-front-matter~ :: Specifies the plain text + front matter. It is passed to ~format~ with arguments =TITLE=, + =DATE=, =KEYWORDS=, =ID= ([[#h:7f918854-5ed4-4139-821f-8ee9ba06ad15][Change the front matter format]]) + +#+findex: denote-date-org-timestamp ++ Function ~denote-date-org-timestamp~ :: Format =DATE= using the Org + inactive timestamp notation. + +#+findex: denote-date-rfc3339 ++ Function ~denote-date-rfc3339~ :: Format =DATE= using the RFC3339 + specification. + +#+findex: denote-date-iso-8601 ++ Function ~denote-date-iso-8601~ :: Format =DATE= according to ISO + 8601 standard. + +#+findex: denote-trim-whitespace ++ Function ~denote-trim-whitespace~ :: Trim whitespace around string + =S=. This can be used in ~denote-file-types~ to format front + mattter. + +#+findex: denote-trim-whitespace-then-quotes ++ Function ~denote-trim-whitespace-then-quotes~ :: Trim whitespace + then quotes around string =S=. This can be used in + ~denote-file-types~ to format front mattter. + +#+findex: denote-format-string-for-org-front-matter ++ Function ~denote-format-string-for-org-front-matter~ :: Return + string =S= as-is for Org or plain text front matter. If =S= is not a + string, return an empty string. + +#+findex: denote-format-string-for-md-front-matter ++ Function ~denote-format-string-for-md-front-matter~ :: Surround + string =S= with quotes. If =S= is not a string, return a literal + emptry string. This can be used in ~denote-file-types~ to format + front mattter. + +#+findex: denote-format-keywords-for-md-front-matter ++ Function ~denote-format-keywords-for-md-front-matter~ :: Format + front matter =KEYWORDS= for markdown file type. =KEYWORDS= is a + list of strings. Consult the ~denote-file-types~ for how this is + used. + +#+findex: denote-format-keywords-for-text-front-matter ++ Function ~denote-format-keywords-for-text-front-matter~ :: Format + front matter =KEYWORDS= for text file type. =KEYWORDS= is a list of + strings. Consult the ~denote-file-types~ for how this is used. + +#+findex: denote-format-keywords-for-org-front-matter ++ Function ~denote-format-keywords-for-org-front-matter~ :: Format + front matter =KEYWORDS= for org file type. =KEYWORDS= is a list of + strings. Consult the ~denote-file-types~ for how this is used. + +#+findex: denote-extract-keywords-from-front-matter ++ Function ~denote-extract-keywords-from-front-matter~ :: Format front + matter =KEYWORDS= for org file type. =KEYWORDS= is a list of + strings. Consult the ~denote-file-types~ for how this is used. + +#+vindex: denote-file-types ++ Variable ~denote-file-types~ :: Alist of ~denote-file-type~ and + their format properties. + + Each element is of the form =(SYMBOL PROPERTY-LIST)=. =SYMBOL= is + one of those specified in ~denote-file-type~ or an arbitrary symbol + that defines a new file type. + + =PROPERTY-LIST= is a plist that consists of the following elements: + + 1. =:extension= is a string with the file extension including the + period. + + 2. =:date-function= is a function that can format a date. See the + functions ~denote--date-iso-8601~, ~denote--date-rfc3339~, and + ~denote--date-org-timestamp~. + + 3. =:front-matter= is either a string passed to ~format~ or a + variable holding such a string. The ~format~ function accepts + four arguments, which come from ~denote~ in this order: =TITLE=, + =DATE=, =KEYWORDS=, =IDENTIFIER=. Read the doc string of + ~format~ on how to reorder arguments. + + 4. =:title-key-regexp= is a regular expression that is used to + retrieve the title line in a file. The first line matching this + regexp is considered the title line. + + 5. =:title-value-function= is the function used to format the raw + title string for inclusion in the front matter (e.g. to surround + it with quotes). Use the ~identity~ function if no further + processing is required. + + 6. =:title-value-reverse-function= is the function used to retrieve + the raw title string from the front matter. It performs the + reverse of =:title-value-function=. + + 7. =:keywords-key-regexp= is a regular expression used to retrieve + the keywords' line in the file. The first line matching this + regexp is considered the keywords' line. + + 8. =:keywords-value-function= is the function used to format the + keywords' list of strings as a single string, with appropriate + delimiters, for inclusion in the front matter. + + 9. =:keywords-value-reverse-function= is the function used to retrieve + the keywords' value from the front matter. It performs the reverse + of the =:keywords-value-function=. + + 10. =:link= is a string, or variable holding a string, that + specifies the format of a link. See the variables + ~denote-org-link-format~, ~denote-md-link-format~. + + 11. =:link-in-context-regexp= is a regular expression that is used + to match the aforementioned link format. See the variables + ~denote-org-link-in-context-regexp~, ~denote-md-link-in-context-regexp~. + + If ~denote-file-type~ is nil, use the first element of this list for + new note creation. The default is ~org~. + +** Link interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:97b8eb9e-07e1-4992-8b59-2a0e3b65574b +:END: + +#+vindex: denote-org-link-format ++ Variable ~denote-org-link-format~ :: Format of Org link to note. + The value is passed to ~format~ with =IDENTIFIER= and =TITLE= + arguments, in this order. Also see ~denote-org-link-in-context-regexp~. + +#+vindex: denote-md-link-format ++ Variable ~denote-md-link-format~ :: Format of Markdown link to note. + The =%N$s= notation used in the default value is for ~format~ as the + supplied arguments are =IDENTIFIER= and =TITLE=, in this order. + Also see ~denote-md-link-in-context-regexp~. + +#+vindex: denote-id-only-link-format ++ Variable ~denote-id-only-link-format~ :: Format of identifier-only + link to note. The value is passed to ~format~ with =IDENTIFIER= as + its sole argument. Also see ~denote-id-only-link-in-context-regexp~. + +#+vindex: denote-org-link-in-context-regexp ++ Variable ~denote-org-link-in-context-regexp~ :: Regexp to match an + Org link in its context. The format of such links is ~denote-org-link-format~. + +#+vindex: denote-md-link-in-context-regexp ++ Variable ~denote-md-link-in-context-regexp~ :: Regexp to match an + Markdown link in its context. The format of such links is ~denote-md-link-format~. + +#+vindex: denote-id-only-link-in-context-regexp ++ Variable ~denote-id-only-link-in-context-regexp~ :: Regexp to match + an identifier-only link in its context. The format of such links is + ~denote-id-only-link-format~. + +#+findex: denote-select-linked-file-prompt ++ Function ~denote-select-linked-file-prompt~ :: Prompt for linked + file among =FILES=. + +#+findex: denote-link-return-links ++ Function ~denote-link-return-links~ :: Return list of links in + current or optional =FILE=. Also see ~denote-link-return-backlinks~. + +#+findex: denote-link-return-backlinks ++ Function ~denote-link-return-backlinks~ :: Return list of backlinks + in current or optional =FILE=. Also see ~denote-link-return-links~. + +#+findex: denote-link-description-with-signature-and-title ++ Function ~denote-link-description-with-signature-and-title~ :: Return + link description for =FILE=. Produce a description as follows: + + - If the region is active, use it as the description. + + - If =FILE= as a signature, then use the + ~denote-link-signature-format~. By default, this looks like + "signature title". + + - If =FILE= does not have a signature, then use its title as the + description. + +#+vindex: denote-link-description-function ++ Variable ~denote-link-description-function~ :: Function to use to + create the description of links. The function specified should take + a =FILE= argument and should return the description as a string. By + default, the title of the file is returned as the description. + +** Xref interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:e824c215-1c31-4b26-b994-7df67341d075 +:END: + +- Function ~denote-retrieve-groups-xref-query~ :: Access location of + xrefs for =QUERY= and group them per file. Limit the search to text + files. + +- Function ~denote-retrieve-files-xref-query~ :: Return sorted, + deduplicated file names with matches for =QUERY= in their contents. + Limit the search to text files. + +- Function ~denote-retrieve-xref-alist~ :: Return xref alist of files + with location of matches for =QUERY=. With optional + =FILES-MATCHING-REGEXP=, limit the list of files accordingly (per + ~denote-directory-files~). At all times limit the search to text + files. + +** Renaming files interface for developers or advanced users +:PROPERTIES: +:CUSTOM_ID: h:66d31f42-6092-493a-97db-83b217db9d96 +:END: + +#+findex: denote-rename-file-prompt +- Function ~denote-rename-file-prompt~ :: Prompt to rename file named + =OLD-NAME= to =NEW-NAME=. If ~denote-rename-confirmations~ does not + contain ~modify-file-name~, return t without prompting. + +#+findex: denote-rename-file-and-buffer ++ Function ~denote-rename-file-and-buffer~ :: Rename file named + =OLD-NAME= to =NEW-NAME=, updating buffer name. + +#+findex: denote-prepend-front-matter ++ Function ~denote-prepend-front-matter~ :: Prepend front matter to + =FILE=. The =TITLE=, =KEYWORDS=, =DATE=, =ID=, =SIGNATURE=, and + =FILE-TYPE= are passed from the renaming command and are used to + construct a new front matter block if appropriate. + +#+findex: denote-rewrite-front-matter ++ Function ~denote-rewrite-front-matter~ :: Rewrite front matter of + note after ~denote-rename-file~ (or related). The =FILE=, =TITLE=, + =KEYWORDS=, =SIGNATURE=, =DATE=, =IDENTIFIER=, and =FILE-TYPE= + arguments are given by the renaming command and are used to construct + new front matter values if appropriate. If ~denote-rename-confirmations~ + contains ~rewrite-front-matter~, prompt to confirm the rewriting of + the front matter. Otherwise produce a ~y-or-n-p~ prompt to that + effect. + +#+findex: denote-add-front-matter-prompt ++ Function ~denote-add-front-matter-prompt~ :: Prompt to add a + front-matter to =FILE=. Return non-nil if a new front matter should + be added. If ~denote-rename-confirmations~ does not contain + ~add-front-matter~, return t without prompting. + +#+findex: denote-rewrite-keywords ++ Function ~denote-rewrite-keywords~ :: Rewrite =KEYWORDS= in =FILE= + outright according to =FILE-TYPE=. Do the same as + ~denote-rewrite-front-matter~ for keywords, but do not ask for + confirmation. With optional =SAVE-BUFFER=, save the buffer + corresponding to =FILE=. This function is for use in the commands + ~denote-keywords-add~, ~denote-keywords-remove~, + ~denote-dired-rename-files~, or related. + +#+findex: denote-update-dired-buffers ++ Function ~denote-update-dired-buffers~ :: Update Dired buffers of + variable ~denote-directory~. Also revert the current Dired buffer + even if it is not inside the ~denote-directory~. Note that the + ~denote-directory~ accepts a directory-local value for what we + internally refer to as "silos" ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directories for notes]]). + +* Troubleshoot Denote in a pristine environment +:PROPERTIES: +:CUSTOM_ID: h:9c4467d5-6480-4681-80fb-cd9717bf8b3b +:END: + +Sometimes we get reports on bugs that may not be actually caused by +some error in the Denote code base. To help gain insight into what +the problem is, we need to be able to reproduce the issue in a minimum +viable system. Below is one way to achieve this. + +1. Find where your =denote.el= file is stored on your filesystem. + +2. Assuming you have already installed the package, one way to do this + is to invoke =M-x find-library= and search for ~denote~. It will + take you to the source file. There do =M-x eval-expression=, which + will bring up a minibuffer prompt. At the prompt evaluate: + +#+begin_example emacs-lisp +(kill-new (expand-file-name (buffer-file-name))) +#+end_example + +3. The above will save the full file system path to your kill ring. + +4. In a terminal emulator or an =M-x shell= buffer execute: + +#+begin_example +emacs -Q +#+end_example + +5. This will open a new instance of Emacs in a pristine environment. + Only the default settings are loaded. + +6. In the =*scratch*= buffer of =emacs -Q=, add your configurations + like the following and try to reproduce the issue: + +#+begin_example emacs-lisp +(require 'denote "/full/path/to/what/you/got/denote.el") + +;; Your configurations here +#+end_example + +Then try to see if your problem still occurs. If it does, then the +fault is with Denote. Otherwise there is something external to it +that we need to account for. Whatever the case, this exercise helps +us get a better sense of the specifics. + +* Contributing +:PROPERTIES: +:CUSTOM_ID: h:1ebe4865-c001-4747-a6f2-0fe45aad71cd +:END: + +Denote is a GNU ELPA package. As such, any significant change to the +code requires copyright assignment to the Free Software Foundation +(more below). + +You do not need to be a programmer to contribute to this package. +Sharing an idea or describing a workflow is equally helpful, as it +teaches us something we may not know and might be able to cover either +by extending Denote or expanding this manual. If you prefer to write a +blog post, make sure you share it with us: we can add a section herein +referencing all such articles. Everyone gets acknowledged +([[#h:f8126820-3b59-49fa-bcc2-73bd60132bb9][Acknowledgements]]). There is no such thing as an "insignificant +contribution"---they all matter. + ++ Package name (GNU ELPA): ~denote~ ++ Official manual: ++ Change log: ++ Git repositories: + + GitHub: + + GitLab: + +If our public media are not suitable, you are welcome to contact me +(Protesilaos) in private: . + +Copyright assignment is a prerequisite to sharing code. It is a simple +process. Check the request form below (please adapt it accordingly). +You must write an email to the address mentioned in the form and then +wait for the FSF to send you a legal agreement. Sign the document and +file it back to them. This could all happen via email and take about a +week. You are encouraged to go through this process. You only need to +do it once. It will allow you to make contributions to Emacs in +general. + +#+begin_example text +Please email the following information to assign@gnu.org, and we +will send you the assignment form for your past and future changes. + +Please use your full legal name (in ASCII characters) as the subject +line of the message. + +REQUEST: SEND FORM FOR PAST AND FUTURE CHANGES + +[What is the name of the program or package you're contributing to?] + +GNU Emacs + +[Did you copy any files or text written by someone else in these changes? +Even if that material is free software, we need to know about it.] + +Copied a few snippets from the same files I edited. Their author, +Protesilaos Stavrou, has already assigned copyright to the Free Software +Foundation. + +[Do you have an employer who might have a basis to claim to own +your changes? Do you attend a school which might make such a claim?] + + +[For the copyright registration, what country are you a citizen of?] + + +[What year were you born?] + + +[Please write your email address here.] + + +[Please write your postal address here.] + + + + + +[Which files have you changed so far, and which new files have you written +so far?] + +#+end_example + +** Wishlist of what we can do to extend Denote +:PROPERTIES: +:CUSTOM_ID: h:044a6a0f-e382-4013-8279-8bf4e64e73c0 +:END: + +These are various ideas to extend Denote. Whether they should be in +the core package or a separate extension is something we can discuss. +I, Protesilaos, am happy to help anyone who wants to do any of this. + +- denote-embark.el :: Provide integration with the ~embark~ package. + This can be for doing something with the identifier/link at point. + For example, it could provide an action to produce backlinks for the + identifier/file we are linking to, not just the current one. + +- denote-transient.el :: The ~transient~ package is built into Emacs + 29 (Denote supports Emacs 28 though). We can use it to define an + alternative to what we have for the menu bar. Perhaps this interface + can used to toggle various options, such as to call ~denote~ with a + different set of prompts. + +- A ~denote-directories~ user option :: This can be either an + extension of the ~denote-directory~ (accept a list of file paths + value) or a new variable. The idea is to let the user define + separate Denote directories which do know about the presence of each + other (unlike silos). This way, a user can have an entry in + =~/Documents/notes/= link to something =~/Git/projects/= and + everything work as if the ~denote-directory~ is set to the =~/= + (with the status quo as of 2024-02-18 08:27 +0200). + +- Encode the day in the identifier :: The idea is to use some coded + reference for Monday, Tuesday, etc. instead of having the generic + =T= in the identifier. For example, Monday is =A= so the identifier + for it is something like =20240219A101522= instead of what we now + have as =20240219T101522=. The old method should still be supported. + Apart from changing a few regular expressions, this does not seem + too complex to me. We would need a user option to opt in to such a + feature. Then tweak the relevant parts. The tricky issue is to + define a mapping of day names to letters/symbols that works for + everyone. Do all countries have a seven-day week, for example? We + need something universally applicable here. + +Anything else? You are welcome to discuss these and/or add to the +list. + +* Publications about Denote +:PROPERTIES: +:CUSTOM_ID: h:ca0c38f9-fa3e-4901-947e-1b589335781d +:END: + +The Emacs community is putting Denote to great use. This section +includes publications that show how people configure their note-taking +setup. If you have a blog post, video, or configuration file about +Denote, feel welcome to tell us about it ([[#h:1ebe4865-c001-4747-a6f2-0fe45aad71cd][Contributing]]). + ++ David Wilson (SystemCrafters): /Generating a Blog Site from Denote + Entries/, 2022-09-09, + ++ David Wilson (SystemCrafters): /Trying Out Prot's Denote, an Org + Roam Alternative?/, 2022-07-15, + ++ Jack Baty: /Keeping my Org Agenda updated based on Denote keywords/, + 2022-11-30, + ++ Jeremy Friesen: /Denote Emacs Configuration/, 2022-10-02, + + ++ Jeremy Friesen: /Exploring the Denote Emacs Package/, 2022-10-01, + + ++ Jeremy Friesen: /Migration Plan for Org-Roam Notes to Denote/, + 2022-10-02, + ++ Jeremy Friesen: /Project Dispatch Menu with Org Mode Metadata, + Denote, and Transient/, 2022-11-19, + + ++ Mohamed Suliman: /Managing a bibliography of BiBTeX entries with + Denote/, 2022-12-20, + ++ Peter Prevos: /Simulating Text Files with R to Test the Emacs Denote + Package/, 2022-07-28, + ++ Peter Prevos: /Emacs Writing Studio/, 2023-10-19. A configuration for authors, using Denote for taking notes, literature reviews and manage collections of images: + - + - + - + - + ++ Stefan Thesing: /Denote as a Zettelkasten/, 2023-03-02, + + ++ Summer Emacs: /An explanation of how I use Emacs/, 2023-05-04, + + +* Alternatives to Denote +:PROPERTIES: +:CUSTOM_ID: h:dbb51a1b-90b8-48e8-953c-e2fb3e36981e +:END: + +What follows is a list of Emacs packages for note-taking. I +(Protesilaos) have not used any of them, as I was manually applying my +file-naming scheme beforehand and by the time those packages were +available I was already hacking on the predecessor of Denote as a means +of learning Emacs Lisp (a package which I called "Unassuming Sidenotes +of Little Significance", aka "USLS" which is pronounced as "U-S-L-S" or +"useless"). As such, I cannot comment at length on the differences +between Denote and each of those packages, beside what I gather from +their documentation. + ++ [[https://github.com/org-roam/org-roam][org-roam]] :: The de facto standard in the Emacs milieu---and rightly + so! It has a massive community, is featureful, and should be an + excellent companion to anyone who is invested in the Org ecosystem + and/or knows what "Roam" is (I don't). It has been explained to me + that Org Roam uses a database to store a cache about your notes. It + otherwise uses standard Org files. The cache helps refer to the same + node through aliases which can provide lots of options. Personally, I + follow a single-topic-per-note approach, so anything beyond that is + overkill. If the database is only for a cache, then maybe that has no + downside, though I am careful with any kind of specialised program as + it creates a dependency. If you ask me about database software in + particular, I have no idea how to use one, let alone debug it or + retrieve data from it if something goes awry (I could learn, but that + is beside the point). + ++ [[https://github.com/localauthor/zk][zk (or zk.el)]] :: Reading its documentation makes me think that this is + Denote's sibling---the two projects have a lot of things in common, + including the preference to rely on plain files and standard tools. + The core difference is that Denote has a strict file-naming scheme. + Other differences in available features are, in principle, matters of + style or circumstance: both packages can have them. As its initials + imply, ZK enables a zettelkasten-like workflow. It does not enforce + it though, letting the user adapt the method to their needs and + requirements. + ++ [[https://github.com/ymherklotz/emacs-zettelkasten][zettelkasten]] :: This is another one of Denote's relatives, at least + insofar as the goal of simplicity is concerned. The major difference + is that according to its documentation "the name of the file that is + created is just a unique ID". This is not consistent with our + file-naming scheme which is all about making sense of your files by + their name alone and being able to visually parse a listing of them + without any kind of specialised tool (e.g. =ls -l= or =ls -C= on the + command-line from inside the ~denote-directory~ give you a + human-readable set of files names, while =find * -maxdepth 0 -type f= + is another approach). + ++ [[https://github.com/EFLS/zetteldeft][zetteldeft]] :: This is a zettelkasten note-taking system built on top + of the =deft= package. Deft provides a search interface to a + directory, in this case the one holding the user's =zetteldeft= notes. + Denote has no such dependency and is not opinionated about how the + user prefers to search/access their notes: use Dired, Grep, the + =consult= package, or whatever else you already have set up for all + things Emacs, not just your notes. + +Searching through =M-x list-packages= for "zettel" brings up more +matches. =zetteldesk= is an extension to Org Roam and, as such, I +cannot possibly know what Org Roam truly misses and what the added-value +of this package is. =neuron-mode= builds on top of an external program +called =neuron=, which I have never used. + +Searching for "note" gives us a few more results. =notes-mode= has +precious little documentation and I cannot tell what it actually does +(as I said in my presentation for LibrePlanet 2022, inadequate docs are +a bug). =side-notes= differs from what we try to do with Denote, as it +basically gives you the means to record your thoughts about some other +project you are working on and keep them on the side: so it and Denote +should not be mutually exclusive. + +If I missed something, please let me know. + +** Alternative implementations and further reading +:PROPERTIES: +:CUSTOM_ID: h:188c0986-f2fa-444f-b493-5429356e75cf +:END: + +This section covers blog posts and implementations from the Emacs +community about the topic of note-taking and file organization. They +may refer to some of the packages covered in the previous section or +provide their custom code ([[#h:dbb51a1b-90b8-48e8-953c-e2fb3e36981e][Alternatives to Denote]]). The list is +unsorted. + ++ José Antonio Ortega Ruiz (aka "jao") explains a note-taking method + that is simple like Denote but differs in other ways. An interesting + approach overall: https://jao.io/blog/simple-note-taking.html. + ++ Jethro Kuan (the main =org-roam= developer) explains their note-taking + techniques: https://jethrokuan.github.io/org-roam-guide/. Good ideas + all round, regardless of the package/code you choose to use. + ++ Karl Voit's tools [[https://github.com/novoid/date2name][date2name]], [[https://github.com/novoid/filetags/][filetags]], [[https://github.com/novoid/appendfilename/][appendfilename]], and + [[https://github.com/novoid/move2archive][move2archive]] provide a Python-based implementation to organize + individual files which do not require Emacs. His approach ([[https://karl-voit.at/managing-digital-photographs/][blog + post]] and his [[https://www.youtube.com/watch?v=rckSVmYCH90][presentation at GLT18]]) has been complemented by [[https://github.com/novoid/memacs][memacs]] + to process e.g., the date of creation of photographs, or the log of + a phone call in a format compatible to org. + +[ Development note: help expand this list. ] + +* Frequently Asked Questions +:PROPERTIES: +:CUSTOM_ID: h:da2944c6-cde6-4c65-8f2d-579305a159bb +:END: + +I (Protesilaos) answer some questions I have received or might get. It +is assumed that you have read the rest of this manual: I will not go +into the specifics of how Denote works. + +** Why develop Denote when PACKAGE already exists? +:PROPERTIES: +:CUSTOM_ID: h:b875450a-ae22-4899-ac23-c10fa9c279bb +:END: + +I wrote Denote because I was using a variant of Denote's file-naming +scheme before I was even an Emacs user (I switched to Emacs from +Tmux+Vim+CLI in the summer of 2019). I was originally inspired by +Jekyll, the static site generator, which I started using for my website +in 2016 (was on WordPress before). Jekyll's files follow the +=YYYY-MM-DD-TITLE.md= pattern. I liked its efficiency relative to the +unstructured mess I had before. Eventually, I started using that scheme +outside the confines of my website's source code. Over time I refined +it and here we are. + +Note-taking is something I take very seriously, as I am a prolific +writer (just check my website, which only reveals the tip of the +iceberg). As such, I need a program that does exactly what I want and +which I know how to extend. I originally tried to use Org capture +templates to create new files with a Denote-style file-naming scheme but +never managed to achieve it. Maybe because ~org-capture~ has some +hard-coded assumptions or I simply am not competent enough to hack on +core Org facilities. Whatever the case, an alternative was in order. + +The existence of PACKAGE is never a good reason for me not to conduct my +own experiments for recreational, educational, or practical purposes. +When the question arises of "why not contribute to PACKAGE instead?" the +answer is that without me experimenting in the first place, I would lack +the skills for such a task. Furthermore, contributing to another +package does not guarantee I get what I want in terms of workflow. + +Whether you should use Denote or not is another matter altogether: +choose whatever you want. + +** Why not rely exclusively on Org? +:PROPERTIES: +:CUSTOM_ID: h:b9831849-5c71-484e-b444-bac19cc13151 +:END: + +I think Org is one of Emacs' killer apps. I also believe it is not the +right tool for every job. When I write notes, I want to focus on +writing. Nothing more. I thus have no need for stuff like org-babel, +scheduling to-do items, clocking time, and so on. The more "mental +dependencies" you add to your workflow, the heavier the burden you carry +and the less focused you are on the task at hand: there is always that +temptation to tweak the markup, tinker with some syntactic construct, +obsess about what ought to be irrelevant to writing as such. + +In technical terms, I also am not fond of Org's code base (I understand +why it is the way it is---just commenting on the fact). Ever tried to +read it? You will routinely find functions that are tens-to-hundreds of +lines long and have all sorts of special casing. As I am not a +programmer and only learnt to write Elisp through trial and error, I +have no confidence in my ability to make Org do what I want at that +level, hence =denote= instead of =org-denote= or something. + +Perhaps the master programmer is one who can deal with complexity and +keep adding to it. I am of the opposite view, as language---code +included---is at its communicative best when it is clear and accessible. + +Make no mistake: I use Org for the agenda and also to write technical +documentation that needs to be exported to various formats, including +this very manual. + +** Why care about Unix tools when you use Emacs? +:PROPERTIES: +:CUSTOM_ID: h:da1e2469-8f04-450b-a379-a854efa80a36 +:END: + +My notes form part of my longer-term storage. I do not want to have to +rely on a special program to be able to read them or filter them. Unix +is universal, at least as far as I am concerned. + +Denote streamlines some tasks and makes things easier in general, which +is consistent with how Emacs provides a layer of interactivity on top of +Unix. Still, Denote's utilities can, in principle, be implemented as +POSIX shell scripts (minus the Emacs-specific parts like fontification +in Dired or the buttonization of links). + +Portability matters. For example, in the future I might own a +smartphone, so I prefer not to require Emacs, Org, or some other +executable to access my files on the go. + +Furthermore, I might want to share those files with someone. If I make +Emacs a requirement, I am limiting my circle to a handful of relatively +advanced users. + +Please don't misinterpret this: I am using Emacs full-time for my +computing and maintain a growing list of packages for it. This is just +me thinking long-term. + +** Why many small files instead of few large ones? +:PROPERTIES: +:CUSTOM_ID: h:7d2e7b8a-d484-4c1d-8688-17f70f242ad7 +:END: + +I have read that Org favours the latter method. If true, I strongly +disagree with it because of the implicit dependency it introduces and +the way it favours machine-friendliness over human-readability in terms +of accessing information. Notes are long-term storage. I might want to +access them on (i) some device with limited features, (ii) print on +paper, (iii) share with another person who is not a tech wizard. + +There are good arguments for few large files, but all either prioritize +machine-friendliness or presuppose the use of sophisticated tools like +Emacs+Org. + +Good luck using =less= on a generic TTY to read a file with a zillion +words, headings, sub-headings, sub-sub-headings, property drawers, and +other constructs! You will not get the otherwise wonderful folding of +headings the way you do in Emacs---do not take such features for +granted. + +My point is that notes should be atomic to help the user---and +potentially the user's family, friends, acquaintances---make sense of +them in a wide range of scenaria. The more program-agnostic your file +is, the better for you and/or everyone else you might share your +writings with. + +Human-readability means that we optimize for what matters to us. If (a) +you are the only one who will ever read your notes, (b) always have +access to good software like Emacs+Org, (c) do not care about printing +on paper, then Denote's model is not for you. Maybe you need to tweak +some ~org-capture~ template to append a new entry to one mega file (I do +that for my Org agenda, by the way, as I explained before about using +the right tool for the job). + +** Does Denote perform well at scale? +:PROPERTIES: +:CUSTOM_ID: h:863f812a-aac7-42ea-83b3-fbbdb58e08d7 +:END: + +Denote does not do anything fancy and has no special requirements: it +uses standard tools to accomplish ordinary tasks. If Emacs can cope +with lots of files, then that is all you need to know: Denote will work. + +To put this to the test, Peter Prevos is running simulations with R that +generate large volumes of notes. You can read the technicalities here: +. +Excerpt: + +#+begin_quote +Using this code I generated ten thousands notes and used this to test +the Denote package to see it if works at a large scale. This tests shows +that Prot's approach is perfectly capable of working with thousands of +notes. +#+end_quote + +Of course, we are always prepared to make refinements to the code, where +necessary, without compromising on the project's principles. + +** I add TODOs to my notes; will many files slow down the Org agenda? +:PROPERTIES: +:CUSTOM_ID: h:63c2f8d4-79ed-4c55-b3ef-e048a05802c0 +:END: + +Yes, many files will slow down the agenda due to how that works. Org +collects all files specified in the ~org-agenda-files~, searches through +their contents for timestamped entries, and then loops through all days +to determine where each entry belongs. The more days and more files, +the longer it takes to build the agenda. Doing this with potentially +hundreds of files will have a noticeable impact on performance. + +This is not a deficiency of Denote. It happens with generic Org files. +The way the agenda is built is heavily favoring the use of a single file +that holds all your timestamped entries (or at least a few such files). +Tens or hundreds of files are inefficient for this job. Plus doing so +has the side-effect of making Emacs open all those files, which you +probably do not need. + +If you want my opinion though, be more forceful with the separation of +concerns. Decouple your knowledge base from your ephemeral to-do list: +Denote (and others) can be used for the former, while you let standard +Org work splendidly for the latter---that is what I do, anyway. + +Org has a powerful linking facility, whether you use ~org-store-link~ or +do it via an ~org-capture~ template. If you want a certain note to be +associated with a task, just store the task in a single =tasks.org= (or +however you name it) and link to the relevant context. + +Do not mix your knowledge base with your to-do items. If you need help +figuring out the specifics of this workflow, you are welcome to ask for +help in our relevant channels ([[#h:1ebe4865-c001-4747-a6f2-0fe45aad71cd][Contributing]]). + +** I want to sort by last modified in Dired, why won't Denote let me? +:PROPERTIES: +:CUSTOM_ID: h:a7fd5e0a-78f7-434e-aa2e-e150479c16e2 +:END: + +Denote does not control how Dired sorts files. I encourage you to read +the manpage of the =ls= executable. It will help you in general, while +it applies to Emacs as well via Dired. The gist is that you can update +the =ls= flags that Dired uses on-the-fly: type =C-u M-x +dired-sort-toggle-or-edit= (=C-u s= by default) and append +=--sort=time= at the prompt. To reverse the order, add the =-r= flag. +The user option ~dired-listing-switches~ sets your default preference. + +For an on-demand sorted and filtered Dired listing of Denote files, +use the command ~denote-sort-dired~ ([[#h:9fe01e63-f34f-4479-8713-f162a5ca865e][Sort files by component]]). + +** How do you handle the last modified case? +:PROPERTIES: +:CUSTOM_ID: h:764b5e87-cd22-4937-b5fc-af3892d6b3d8 +:END: + +Denote does not insert any meta data or heading pertaining to edits in +the file. I am of the view that these either do not scale well or are +not descriptive enough. Suppose you use a "lastmod" heading with a +timestamp: which lines where edited and what did the change amount to? + +This is where an external program can be helpful. Use a Version Control +System, such as Git, to keep track of all your notes. Every time you +add a new file, record the addition. Same for post-creation edits. +Your VCS will let you review the history of those changes. For +instance, Emacs' built-in version control framework has a command that +produces a log of changes for the current file: =M-x vc-print-log=, +bound to =C-x v l= by default. From there one can access the +corresponding diff output (use =M-x describe-mode= (=C-h m=) in an +unfamiliar buffer to learn more about it). With Git in particular, +Emacs users have the option of the all-round excellent =magit= package. + +In short: let Denote (or equivalent) create notes and link between them, +the file manager organise and provide access to files, search programs +deal with searching and narrowing, and version control software handle +the tracking of changes. + +** Why are some Org links opening outside Emacs? +:PROPERTIES: +:CUSTOM_ID: h:4f354db1-aa78-47fd-ac60-c3d1e0f0b0a4 +:END: + +Org has its own mechanism to determine how best to open a link. This +affects the =file:= link type, but also the =denote:= one (which is +designed to be as close to =file:= as possible). + +When following a link, Org usually displays the data in an Emacs +buffer, though it might launch an external application instead. The +idea is to use a specialised program when that is relevant, such as to +display a video. Though there can be scenaria the user does not like, +such as when Org decides to load =.md= or =.html= files with an +external app. To compound the problem, users can name any file type +using the Denote file-naming scheme, including images, PDFs, videos, +and more ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). + +To instruct Org to stay in Emacs for such cases, the user needs to +modify the variable ~org-file-apps~, which is not specific to Denote. +As one use-case, ~org-file-apps~ associates a regular expression to +match file names with a method on how to display them (do =M-x +describe-variable= and then search for ~org-file-apps~ to read its +documentation). Thus, the user can use something like the following +in their Org or Denote configuration: + +#+begin_src emacs-lisp +;; Tell Org to use Emacs when opening files that end in .md +(add-to-list 'org-file-apps '("\\.md\\'" . emacs)) + +;; Do the same for .html +(add-to-list 'org-file-apps '("\\.html\\'" . emacs)) +#+end_src + +Each of these adds a new entry to the existing value of that user +option. Replace =md= or =html= with the desired file type extension. + +** Speed up backlinks' or query links' buffer creation? +:PROPERTIES: +:CUSTOM_ID: h:893eec49-d7be-4603-bcff-fcc247244011 +:END: + +Denote leverages the built-in =xref= library to search for the +identifier of the current file and return any links to it. For users +of Emacs version 28 or higher, there exists a user option to specify +the program that performs this search: ~xref-search-program~. The +default is =grep=, which can be slow, though one may opt for =ugrep=, +=ripgrep=, or even specify something else (read the doc string of that +user option for the details). + +Try either for these for better results: + +#+begin_src emacs-lisp +(setq xref-search-program 'ripgrep) + +;; OR + +(setq xref-search-program 'ugrep) +#+end_src + +To use whatever executable is available on your system, use something +like this: + +#+begin_src emacs-lisp +;; Prefer ripgrep, then ugrep, and fall back to regular grep. +(setq xref-search-program + (cond + ((or (executable-find "ripgrep") + (executable-find "rg")) + 'ripgrep) + ((executable-find "ugrep") + 'ugrep) + (t + 'grep))) +#+end_src + +** Why do I get "Search failed with status 1" when I search for backlinks? +:PROPERTIES: +:CUSTOM_ID: h:42f6b07e-5956-469a-8294-17f9cf62eb2b +:END: + +Denote uses [[info:emacs#Xref][Emacs' Xref]] to find backlinks. Xref requires ~xargs~ and +one of ~grep~ or ~ripgrep~, depending on your configuration. + +This is usually not an issue on *nix systems, but the necessary +executables are not available on Windows Emacs distributions. Please +ensure that you have both ~xargs~ and either ~grep~ or ~ripgrep~ +available within your ~PATH~ environment variable. + +If you have ~git~ on Windows installed, then you may use the following +code (adjust the git's installation path if necessary): +#+begin_src emacs-lisp + (setenv "PATH" (concat (getenv "PATH") ";" "C:\\Program Files\\Git\\usr\\bin")) +#+end_src + +** Why do I get a double =#+title= in Doom Emacs? +:PROPERTIES: +:CUSTOM_ID: h:0f737b7d-40e6-46a7-b1db-117c0ffcbfef +:END: + +Doom Emacs provides a set of bespoke templates for Org. One of those +prefills any new Org file with a =#+title= field. So when Denote +creates a new Org file and inserts front matter to it, it inevitably +adds an extra title to the existing one. + +This is not a Denote problem. We can only expect a new file to be +empty by default. Check how to disable the relevant module in your +Doom Emacs configuration file. + +* Acknowledgements +:PROPERTIES: +:CUSTOM_ID: h:f8126820-3b59-49fa-bcc2-73bd60132bb9 +:END: +#+cindex: Contributors + +Denote is meant to be a collective effort. Every bit of help matters. + ++ Author/maintainer :: Protesilaos Stavrou. + ++ Contributions to code or the manual :: Abdul-Lateef Haji-Ali, Abin + Simon, Adam Růžička, Alan Schmitt, Alexandre Rousseau, Ashton + Wiersdorf, Aziz, Benjamin Kästner, Bruno Boal, Charanjit Singh, + Claudio Migliorelli, Clemens Radermacher, Colin McLear, Damien + Cassou, Eduardo Grajeda, Elias Storms, Eshel Yaron, Florian, Glenna + D., Graham Marlow, Hilde Rhyne, Ivan Sokolov, Jack Baty, Jakub + Szczerbowski, Jean-Charles Bagneris, Jean-Philippe Gagné Guay, + Jianwei Hou, Joseph Turner, Jürgen Hötzel, Kaushal Modi, Kai von + Fintel, Kierin Bell, Kostas Andreadis, Kristoffer Balintona, Kyle + Meyer, Laurent Gatto, Lucas Quintana, Maikol Solis, Marc Fargas, + Matthew Lemon, Noboru Ota (nobiot), Norwid Behrnd, Octavian, Peter + Prevos, Philip Kaludercic, Quiliro Ordóñez, Stephen R. Kifer, Stefan + Monnier, Stefan Thesing, Thibaut Benjamin, Tomasz Hołubowicz, + TomoeMami , Vedang Manerikar, Wesley Harvey, Zhenxu Xu, arsaber101, + bryanrinders, eum3l, ezchi, jarofromel, leinfink (Henrik), l-o-l-h + (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, pmenair, + relict007, skissue. + ++ Ideas and/or user feedback :: Abin Simon, Aditya Yadav, Alan + Schmitt, Aleksandr Vityazev, Alex Griffin, Alex Hirschfeld, Alexis + Purslane, Alfredo Borrás, Alp Eren Kose, André Bering, Ashton + Wiersdorf, Benjamin Kästner, Claudio Migliorelli, Claudiu Tănăselia, + Colin McLear, Cosmin-Octavian C, Damien Cassou, Elias Storms, + Federico Stilman, Florian, Frédéric Willem Frank Ehmsen, Glenna D., + Guo Yong, Hanspeter Gisler Harold Ollivier, IceAsteroid, Jack Baty, + Jay Rajput, Jean-Charles Bagneris, Jeff Valk, Jens Östlund, Jeremy + Friesen, Jonathan Sahar, Johan Bolmsjö, Jonas Großekathöfer, + Jousimies, Juanjo Presa, Julian Hoch, Kai von Fintel, Kaushal Modi, + Kolmas, Lukas C. Bossert, M. Hadi Timachi, Maikol Solis, Mark Olson, + Mirko Hernandez, Niall Dooley, Nick Bell, Oliver Epper, Paul van + Gelder, Peter Prevos, Peter Smith, Riccardo Giannitrapani, Samuel W. + Flint, Sergio Rey, Suhail Singh, Shreyas Ragavan, Stefan Thesing, + Summer Emacs, Sven Seebeck, Taoufik, TJ Stankus, Vick (VicZz), + Viktor Haag, Vineet C. Kulkarni, Wade Mealing, Wilf, Yi Liu, Ypot, + atanasj, azegas, babusri, bdillahu, coherentstate, doolio, duli, + drcxd, elge70, elliottw, fingerknight, hpgisler, hyperfocus1337, + jtpavlock, juh, leafarbelm, mentalisttraceur, pRot0ta1p, rbenit68, + relict007, sarcom-sar, sienic, skissue, sundar bp, + yetanotherfossman, zadca123 + +Special thanks to Peter Povinec who helped refine the file-naming +scheme, which is the cornerstone of this project. + +Special thanks to Jean-Philippe Gagné Guay for the numerous +contributions to the code base. + +* GNU Free Documentation License +:PROPERTIES: +:APPENDIX: t +:CUSTOM_ID: h:2d84e73e-c143-43b5-b388-a6765da974ea +:END: + +#+texinfo: @include doclicense.texi + +#+begin_export html +
+
+                GNU Free Documentation License
+                 Version 1.3, 3 November 2008
+
+
+ Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
+     
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense.  It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does.  But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book.  We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License.  Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein.  The "Document", below,
+refers to any such manual or work.  Any member of the public is a
+licensee, and is addressed as "you".  You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject.  (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.)  The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License.  If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant.  The Document may contain zero
+Invariant Sections.  If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License.  A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters.  A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text.  A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification.  Examples of
+transparent image formats include PNG, XCF and JPG.  Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page.  For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+The "publisher" means any person or entity that distributes copies of
+the Document to the public.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language.  (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".)  To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document.  These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no
+other conditions whatsoever to those of this License.  You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute.  However, you may accept
+compensation in exchange for copies.  If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover.  Both covers must also clearly and legibly identify
+you as the publisher of these copies.  The front cover must present
+the full title with all words of the title equally prominent and
+visible.  You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to
+give them a chance to provide you with an updated version of the
+Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it.  In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+   from that of the Document, and from those of previous versions
+   (which should, if there were any, be listed in the History section
+   of the Document).  You may use the same title as a previous version
+   if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+   responsible for authorship of the modifications in the Modified
+   Version, together with at least five of the principal authors of the
+   Document (all of its principal authors, if it has fewer than five),
+   unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+   Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+   adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+   giving the public permission to use the Modified Version under the
+   terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+   and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+   to it an item stating at least the title, year, new authors, and
+   publisher of the Modified Version as given on the Title Page.  If
+   there is no section Entitled "History" in the Document, create one
+   stating the title, year, authors, and publisher of the Document as
+   given on its Title Page, then add an item describing the Modified
+   Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+   public access to a Transparent copy of the Document, and likewise
+   the network locations given in the Document for previous versions
+   it was based on.  These may be placed in the "History" section.
+   You may omit a network location for a work that was published at
+   least four years before the Document itself, or if the original
+   publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+   Preserve the Title of the section, and preserve in the section all
+   the substance and tone of each of the contributor acknowledgements
+   and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+   unaltered in their text and in their titles.  Section numbers
+   or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements".  Such a section
+   may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+   or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant.  To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version.  Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity.  If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy.  If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications".  You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other
+documents released under this License, and replace the individual
+copies of this License in the various documents with a single copy
+that is included in the collection, provided that you follow the rules
+of this License for verbatim copying of each of the documents in all
+other respects.
+
+You may extract a single document from such a collection, and
+distribute it individually under this License, provided you insert a
+copy of this License into the extracted document, and follow this
+License in all other respects regarding verbatim copying of that
+document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections.  You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers.  In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense, or distribute it is void, and
+will automatically terminate your rights under this License.
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, receipt of a copy of some or all of the same material does
+not give you any rights to use it.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions of the
+GNU Free Documentation License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in
+detail to address new problems or concerns.  See
+https://www.gnu.org/licenses/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation.  If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation.  If the Document
+specifies that a proxy can decide which future versions of this
+License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the
+Document.
+
+11. RELICENSING
+
+"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
+World Wide Web server that publishes copyrightable works and also
+provides prominent facilities for anybody to edit those works.  A
+public wiki that anybody can edit is an example of such a server.  A
+"Massive Multiauthor Collaboration" (or "MMC") contained in the site
+means any set of copyrightable works thus published on the MMC site.
+
+"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
+license published by Creative Commons Corporation, a not-for-profit
+corporation with a principal place of business in San Francisco,
+California, as well as future copyleft versions of that license
+published by that same organization.
+
+"Incorporate" means to publish or republish a Document, in whole or in
+part, as part of another Document.
+
+An MMC is "eligible for relicensing" if it is licensed under this
+License, and if all works that were first published under this License
+somewhere other than this MMC, and subsequently incorporated in whole or
+in part into the MMC, (1) had no cover texts or invariant sections, and
+(2) were thus incorporated prior to November 1, 2008.
+
+The operator of an MMC Site may republish an MMC contained in the site
+under CC-BY-SA on the same site at any time before August 1, 2009,
+provided the MMC is eligible for relicensing.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+    Copyright (c)  YEAR  YOUR NAME.
+    Permission is granted to copy, distribute and/or modify this document
+    under the terms of the GNU Free Documentation License, Version 1.3
+    or any later version published by the Free Software Foundation;
+    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+    A copy of the license is included in the section entitled "GNU
+    Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+    with the Invariant Sections being LIST THEIR TITLES, with the
+    Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
+
+#+end_export + +#+html: blob - /dev/null blob + d19a789ff27c7479ccb21f61f543940c0edde9c4 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/denote-autoloads.el @@ -0,0 +1,755 @@ +;;; denote-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*- +;; Generated by the `loaddefs-generate' function. + +;; This file is part of GNU Emacs. + +;;; Code: + +(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path))) + + + +;;; Generated autoloads from denote.el + + (put 'denote-directory 'safe-local-variable (lambda (val) (or (stringp val) (eq val 'local) (eq val 'default-directory)))) + (put 'denote-known-keywords 'safe-local-variable #'listp) + (put 'denote-infer-keywords 'safe-local-variable (lambda (val) (or val (null val)))) +(autoload 'denote-sort-files "denote" "\ +Returned sorted list of Denote FILES. + +With COMPONENT as a symbol among `denote-sort-components', +sort files based on the corresponding file name component. + +With COMPONENT as the symbol of a function, use it to perform the +sorting. In this case, the function is called with two arguments, as +described by `sort'. + +With COMPONENT as a nil value keep the original date-based +sorting which relies on the identifier of each file name. + +With optional REVERSE as a non-nil value, reverse the sort order. + +(fn FILES COMPONENT &optional REVERSE)") +(autoload 'denote-sort-dired "denote" "\ +Produce Dired buffer with sorted files from variable `denote-directory'. +When called interactively, prompt for FILES-MATCHING-REGEXP and, +depending on the value of the user option `denote-sort-dired-extra-prompts', +also prompt for SORT-BY-COMPONENT, REVERSE, and EXCLUDE-REGEXP. + +1. FILES-MATCHING-REGEXP limits the list of Denote files to + those matching the provided regular expression. + +2. SORT-BY-COMPONENT sorts the files by their file name component (one + among `denote-sort-components'). If it is nil, sorting is performed + according to the user option `denote-sort-dired-default-sort-component', + falling back to the identifier. + +3. REVERSE is a boolean to reverse the order when it is a non-nil value. + If `denote-sort-dired-extra-prompts' is configured to skip this + prompt, then the sorting is done according to the user option + `denote-sort-dired-default-reverse-sort', falling back to + nil (i.e. no reverse sort). + +4. EXCLUDE-REGEXP excludes the files that match the given regular + expression. This is done after FILES-MATCHING-REGEXP and + OMIT-CURRENT have been applied. + +When called from Lisp, the arguments are a string, a symbol among +`denote-sort-components', a non-nil value, and a string, respectively. + +(fn FILES-MATCHING-REGEXP SORT-BY-COMPONENT REVERSE EXCLUDE-REGEXP)" t) +(autoload 'denote "denote" "\ +Create a new note with the appropriate metadata and file name. + +Run the `denote-after-new-note-hook' after creating the new note and +return its path. Before returning the path, determine what needs to be +done to the buffer, in accordance with the user option `denote-kill-buffers'. + +When called interactively, the metadata and file name are prompted +according to the value of `denote-prompts'. + +When called from Lisp, all arguments are optional. + +- TITLE is a string or a function returning a string. + +- KEYWORDS is a list of strings. The list can be empty or the + value can be set to nil. + +- FILE-TYPE is a symbol among those described in the user option + `denote-file-type'. + +- DIRECTORY is a string representing the path to either the + value of the variable `denote-directory' or a subdirectory + thereof. The subdirectory must exist: Denote will not create + it. If DIRECTORY does not resolve to a valid path, the + variable `denote-directory' is used instead. + +- DATE is a string representing a date like 2022-06-30 or a date + and time like 2022-06-16 14:30. A nil value or an empty string + is interpreted as the `current-time'. + +- TEMPLATE is a symbol which represents the key of a cons cell in + the user option `denote-templates'. The value of that key is + inserted to the newly created buffer after the front matter. + +- SIGNATURE is a string or a function returning a string. + +(fn &optional TITLE KEYWORDS FILE-TYPE DIRECTORY DATE TEMPLATE SIGNATURE)" t) +(autoload 'denote-type "denote" "\ +Create note while prompting for a file type. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `file-type' prompt appended to its existing prompts." t) +(function-put 'denote-type 'interactive-only 't) +(autoload 'denote-date "denote" "\ +Create note while prompting for a date. + +The date can be in YEAR-MONTH-DAY notation like 2022-06-30 or +that plus the time: 2022-06-16 14:30. When the user option +`denote-date-prompt-use-org-read-date' is non-nil, the date +prompt uses the more powerful Org+calendar system. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `date' prompt appended to its existing prompts." t) +(function-put 'denote-date 'interactive-only 't) +(autoload 'denote-subdirectory "denote" "\ +Create note while prompting for a subdirectory. + +Available candidates include the value of the variable +`denote-directory' and any subdirectory thereof. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `subdirectory' prompt appended to its existing prompts." t) +(function-put 'denote-subdirectory 'interactive-only 't) +(autoload 'denote-template "denote" "\ +Create note while prompting for a template. + +Available candidates include the keys in the `denote-templates' +alist. The value of the selected key is inserted in the newly +created note after the front matter. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `template' prompt appended to its existing prompts." t) +(function-put 'denote-template 'interactive-only 't) +(autoload 'denote-signature "denote" "\ +Create note while prompting for a file signature. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `signature' prompt appended to its existing prompts." t) +(function-put 'denote-signature 'interactive-only 't) +(autoload 'denote-region "denote" "\ +Call `denote' and insert therein the text of the active region. + +Note that, currently, `denote-save-buffers' and +`denote-kill-buffers' are NOT respected. The buffer is not +saved or killed at the end of `denote-region'." t) +(function-put 'denote-region 'interactive-only 't) +(autoload 'denote-open-or-create "denote" "\ +Visit TARGET file in variable `denote-directory'. +If file does not exist, invoke `denote' to create a file. In that case, +use the last input at the file prompt as the default value of the title +prompt. + +(fn TARGET)" t) +(autoload 'denote-open-or-create-with-command "denote" "\ +Like `denote-open-or-create' but use one of the `denote-commands-for-new-notes'." t) +(function-put 'denote-open-or-create-with-command 'interactive-only 't) +(autoload 'denote-rename-file "denote" "\ +Rename file and update existing front matter if appropriate. + +Always rename the file where it is located in the file system: +never move it to another directory. + +If in Dired, consider FILE to be the one at point, else the +current file, else prompt with minibuffer completion for one. +When called from Lisp, FILE is a file system path represented as +a string. + +If FILE has a Denote-compliant identifier, retain it while +updating components of the file name referenced by the user +option `denote-prompts'. By default, these are the TITLE and +KEYWORDS. The SIGNATURE is another one. When called from Lisp, +TITLE and SIGNATURE are strings, while KEYWORDS is a list of +strings. + +If there is no identifier, create an identifier based on the +following conditions: + +1. If the `denote-prompts' includes an entry for date prompts, + then prompt for DATE and take its input to produce a new + identifier. For use in Lisp, DATE must conform with + `denote-valid-date-p'. + +2. If DATE is nil (e.g. when `denote-prompts' does not include a + date entry), use the file attributes to determine the last + modified date of FILE and format it as an identifier. + +3. As a fallback, derive an identifier from the current date and + time. + +4. At any rate, if the resulting identifier is not unique among + the files in the variable `denote-directory', increment it + such that it becomes unique. + +In interactive use, and assuming `denote-prompts' includes a +title entry, make the TITLE prompt have prefilled text in the +minibuffer that consists of the current title of FILE. The +current title is either retrieved from the front matter (such as +the #+title in Org) or from the file name. + +Do the same for the SIGNATURE prompt, subject to `denote-prompts', +by prefilling the minibuffer with the current signature of FILE, +if any. + +Same principle for the KEYWORDS prompt: convert the keywords in +the file name into a comma-separated string and prefill the +minibuffer with it (the KEYWORDS prompt accepts more than one +keywords, each separated by a comma, else the `crm-separator'). + +For all prompts, interpret an empty input as an instruction to +remove that file name component. For example, if a TITLE prompt +is available and FILE is 20240211T093531--some-title__keyword1.org +then rename FILE to 20240211T093531__keyword1.org. + +In interactive use, if there is no entry for a file name +component in `denote-prompts', keep it as-is. + +When called from Lisp, the special symbol `keep-current' can be +used for the TITLE, KEYWORDS, SIGNATURE and DATE parameters to +keep them as-is. + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the + empty minibuffer contents as they are, though popular packages + like `vertico' use the first available completion candidate + instead. For `vertico', the user must either move one up to + select the prompt and then type RET there with empty contents, + or use the command `vertico-exit-input' with empty contents. + That Vertico command is bound to M-RET as of this writing on + 2024-02-13 08:08 +0200. ] + +As a final step, ask for confirmation, showing the difference +between old and new file names. Do not ask for confirmation if +the user option `denote-rename-confirmations' does not contain +the symbol `modify-file-name'. + +If FILE has front matter for TITLE and KEYWORDS, ask to rewrite +their values in order to reflect the new input, unless +`denote-rename-confirmations' lacks `rewrite-front-matter'. When +the `denote-save-buffers' is nil (the default), do not save the +underlying buffer, thus giving the user the option to +double-check the result, such as by invoking the command +`diff-buffer-with-file'. The rewrite of the TITLE and KEYWORDS +in the front matter should not affect the rest of the front +matter. + +If the file does not have front matter but is among the supported file +types (per the user option `denote-file-type'), add front matter to the +top of it and leave the buffer unsaved for further inspection. Save the +buffer if `denote-save-buffers' is non-nil. + +When `denote-kill-buffers' is t or `on-rename', kill the buffer +if it was not already being visited before the rename operation. + +For the front matter of each file type, refer to the variables: + +- `denote-org-front-matter' +- `denote-text-front-matter' +- `denote-toml-front-matter' +- `denote-yaml-front-matter' + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +Run the `denote-after-rename-file-hook' after renaming FILE. + +This command is intended to (i) rename Denote files, (ii) convert +existing supported file types to Denote notes, and (ii) rename +non-note files (e.g. PDF) that can benefit from Denote's +file-naming scheme. + +For a version of this command that works with multiple files +one-by-one, use `denote-dired-rename-files'. + +(fn FILE TITLE KEYWORDS SIGNATURE DATE)" t) +(autoload 'denote-dired-rename-files "denote" "\ +Rename Dired marked files same way as `denote-rename-file'. +Rename each file in sequence, making all the relevant prompts. +Unlike `denote-rename-file', do not prompt for confirmation of +the changes made to the file: perform them outright (same as +setting `denote-rename-confirmations' to a nil value)." '(dired-mode)) +(function-put 'denote-dired-rename-files 'interactive-only 't) +(autoload 'denote-dired-rename-marked-files-with-keywords "denote" "\ +Rename marked files in Dired to a Denote file name by writing keywords. + +Specifically, do the following: + +- retain the file's existing name and make it the TITLE field, + per Denote's file-naming scheme; + +- sluggify the TITLE, according to our conventions (check the + user option `denote-file-name-slug-functions'); + +- prepend an identifier to the TITLE; + +- preserve the file's extension, if any; + +- prompt once for KEYWORDS and apply the user's input to the + corresponding field in the file name, rewriting any keywords + that may exist while removing keywords that do exist if + KEYWORDS is empty; + +- add or rewrite existing front matter to the underlying file, if it is + recognized as a Denote note (per the user option `denote-file-type'), + such that it includes the new keywords. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +Run the `denote-after-rename-file-hook' after renaming is done. + +Also see the specialized commands to only add or remove keywords: + +- `denote-dired-rename-marked-files-add-keywords'. +- `denote-dired-rename-marked-files-remove-keywords'." '(dired-mode)) +(function-put 'denote-dired-rename-marked-files-with-keywords 'interactive-only 't) +(autoload 'denote-dired-rename-marked-files-add-keywords "denote" "\ +Like `denote-dired-rename-marked-files-with-keywords' to only add keywords." '(dired-mode)) +(function-put 'denote-dired-rename-marked-files-add-keywords 'interactive-only 't) +(autoload 'denote-dired-rename-marked-files-remove-keywords "denote" "\ +Like `denote-dired-rename-marked-files-with-keywords' to only remove keywords." '(dired-mode)) +(function-put 'denote-dired-rename-marked-files-remove-keywords 'interactive-only 't) +(autoload 'denote-rename-file-using-front-matter "denote" "\ +Rename FILE using its front matter as input. +When called interactively, FILE is the variable `buffer-file-name' or +the Dired file at point, which is subsequently inspected for the +requisite front matter. It is thus implied that the FILE has a file +type that is supported by Denote, per the user option `denote-file-type'. + +The values of `denote-rename-confirmations', +`denote-save-buffers' and `denote-kill-buffers' are respected. + +Only the front matter lines that appear in the front matter template (as +defined in `denote-file-types') will be handled. + +To change the identifier (date) of the note with this command, the +identifier line (if present) of the front matter must be modified. +Modifying the date line has no effect. + +While this command generally does not modify the front matter, there are +exceptions. The value of the `date' line will follow that of the +`identifier' line. If they are both in the front matter template and +the `date' line is missing, it will be added again. Similarly, if they +are both in the front matter template and the `date' line is present and +the `identifier' line has been removed, the `date' line will be removed +as well. Also, if the keywords are out of order and +`denote-sort-keywords' is non-nil, they will be sorted. There will be a +prompt for this if `denote-rename-confirmations' contains +`rewrite-front-matter'. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +(fn FILE)" t) +(autoload 'denote-dired-rename-marked-files-using-front-matter "denote" "\ +Call `denote-rename-file-using-front-matter' over the Dired marked files. +Refer to the documentation of that command for the technicalities. + +Marked files must count as notes for the purposes of Denote, which means +that they at least have an identifier in their file name and use a +supported file type, per the user option `denote-file-type'. Files that +do not meet this criterion are ignored because Denote cannot know if +they have front matter and what that may be." '(dired-mode)) +(autoload 'denote-change-file-type-and-front-matter "denote" "\ +Change file type of FILE and add an appropriate front matter. + +If in Dired, consider FILE to be the one at point, else the +current file, else prompt with minibuffer completion for one. + +Add a front matter in the format of the NEW-FILE-TYPE at the +beginning of the file. + +Retrieve the title of FILE from a line starting with a title +field in its front matter, depending on the previous file +type (e.g. #+title for Org). The same process applies for +keywords. + +As a final step, ask for confirmation, showing the difference +between old and new file names. + +Important note: No attempt is made to modify any other elements +of the file. This needs to be done manually. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +(fn FILE NEW-FILE-TYPE)" t) +(autoload 'denote-dired-mode "denote" "\ +Fontify all Denote-style file names. + +Add this or `denote-dired-mode-in-directories' to +`dired-mode-hook'. + +This is a minor mode. If called interactively, toggle the `Denote-Dired +mode' mode. If the prefix argument is positive, enable the mode, and if +it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable the +mode if ARG is nil, omitted, or is a positive number. Disable the mode +if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate the variable `denote-dired-mode'. + +The mode's hook is called both when the mode is enabled and when it is +disabled. + +(fn &optional ARG)" t) +(autoload 'denote-dired-mode-in-directories "denote" "\ +Enable `denote-dired-mode' in `denote-dired-directories'. +Add this function to `dired-mode-hook'. + +If `denote-dired-directories-include-subdirectories' is non-nil, +also enable it in all subdirectories.") +(autoload 'denote-link "denote" "\ +Create link to FILE note in variable `denote-directory' with DESCRIPTION. + +When called interactively, prompt for FILE using completion. In this +case, derive FILE-TYPE from the current buffer. FILE-TYPE is used to +determine the format of the link. + +Return the DESCRIPTION of the link in the format specified by +`denote-link-description-format'. The default is to return the text of +the active region or the title of the note (plus the signature if +present). + +With optional ID-ONLY as a non-nil argument, such as with a universal +prefix (\\[universal-argument]), insert links with just the identifier +and no further description. In this case, the link format is always +[[denote:IDENTIFIER]]. + +If the DESCRIPTION is empty, format the link the same as with ID-ONLY. + +When called from Lisp, FILE is a string representing a full file system +path. FILE-TYPE is a symbol as described in the user option +`denote-file-type'. DESCRIPTION is a string. Whether the caller treats +the active region specially, is up to it. + +(fn FILE FILE-TYPE DESCRIPTION &optional ID-ONLY)" t) +(autoload 'denote-find-link "denote" "\ +Use minibuffer completion to visit linked file. +Also see `denote-find-backlink'." t) +(function-put 'denote-find-link 'interactive-only 't) +(autoload 'denote-link-after-creating "denote" "\ +Create new note in the background and link to it directly. + +Use `denote' interactively to produce the new note. Its doc +string explains which prompts will be used and under what +conditions. + +With optional ID-ONLY as a prefix argument create a link that +consists of just the identifier. Else try to also include the +file's title. This has the same meaning as in `denote-link'. + +For a variant of this, see `denote-link-after-creating-with-command'. + +IMPORTANT NOTE: Normally, `denote' does not save the buffer it +produces for the new note. This is a safety precaution to not +write to disk unless the user wants it (e.g. the user may choose +to kill the buffer, thus cancelling the creation of the note). +However, for this command the creation of the note happens in the +background and the user may miss the step of saving their buffer. +We thus have to save the buffer in order to (i) establish valid +links, and (ii) retrieve whatever front matter from the target +file. Though see `denote-save-buffer-after-creation'. + +(fn &optional ID-ONLY)" t) +(autoload 'denote-link-after-creating-with-command "denote" "\ +Like `denote-link-after-creating' but prompt for note-making COMMAND. +Use this to, for example, call `denote-signature' so that the +newly created note has a signature as part of its file name. + +Optional ID-ONLY has the same meaning as in the command +`denote-link-after-creating'. + +(fn COMMAND &optional ID-ONLY)" t) +(autoload 'denote-link-or-create "denote" "\ +Use `denote-link' on TARGET file, creating it if necessary. + +If TARGET file does not exist, call `denote-link-after-creating' which +runs the `denote' command interactively to create the file. The +established link will then be targeting that new file. In that case, +use the last input at the file prompt as the default value of the title +prompt. + +With optional ID-ONLY as a prefix argument create a link that +consists of just the identifier. Else try to also include the +file's title. This has the same meaning as in `denote-link'. + +(fn TARGET &optional ID-ONLY)" t) +(autoload 'denote-grep "denote" "\ +Search QUERY in the content of Denote files. +QUERY should be a regular expression accepted by `xref-search-program'. + +The files to search for are those returned by `denote-directory-files' +with a non-nil TEXT-ONLY argument. + +Results are put in a buffer which allows folding and further +filtering (see the manual for details). + +You can insert a link to a grep search in any note by using the command +`denote-query-contents-link'. + +(fn QUERY)" t) +(autoload 'denote-grep-marked-dired-files "denote" "\ +Do the equivalent of `denote-grep' for QUERY in marked Dired files. + +(fn QUERY)" t) +(autoload 'denote-grep-files-referenced-in-region "denote" "\ +Perform `denote-grep' QUERY in files referenced between START and END. +When called interactively, prompt for QUERY. Also get START and END as +the buffer positions that delimit the marked region. When called from +Lisp, QUERY is a string, while START and END are buffer positions, as +integers. + +Find references to files by their identifier. This includes links with +just the identifier (as described in `denote-link' and related), links +written by an Org dynamic block (see the `denote-org' package), or even +file listings such as those of `dired' and the command-line `ls' program. + +(fn QUERY START END)" t) +(autoload 'denote-backlinks "denote" "\ +Produce a buffer with backlinks to the current note. + +Show the names of files linking to the current file. Include the +context of each link if the user option `denote-backlinks-show-context' +is non-nil. + +Place the buffer below the current window or wherever the user option +`denote-backlinks-display-buffer-action' specifies." t) +(autoload 'denote-find-backlink "denote" "\ +Use minibuffer completion to visit backlink to current file. +Alo see `denote-find-link'." t) +(function-put 'denote-find-backlink 'interactive-only 't) +(autoload 'denote-query-contents-link "denote" "\ +Insert query link for file contents. +Prompt for QUERY or use the text of the active region. When the user +follows this link, place any matches in a separate buffer (using the +built-in Xref mechanism). This is the equivalent of a Unix grep command +across the variable `denote-directory'. + +(fn QUERY)" t) +(autoload 'denote-query-filenames-link "denote" "\ +Insert query link for file names. +Prompt for QUERY or use the text of the active region. When the user +follows this link, place any matches in a separate buffer (using the +built-in Dired mechanism). This is the equivalent of a Unix find +command across the variable `denote-directory'. + +(fn QUERY)" t) +(autoload 'denote-fontify-links-mode-maybe "denote" "\ +Enable `denote-fontify-links-mode' in a denote file unless in `org-mode'.") +(autoload 'denote-fontify-links-mode "denote" "\ +A minor mode to fontify and fold Denote links. + +Enabled this mode only when the current buffer is a Denote note and the +major mode is not `org-mode' (or derived therefrom). Consider using +`denote-fontify-links-mode-maybe' for this purpose. + +This is a minor mode. If called interactively, toggle the +`Denote-Fontify-Links mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable the +mode if ARG is nil, omitted, or is a positive number. Disable the mode +if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate the variable `denote-fontify-links-mode'. + +The mode's hook is called both when the mode is enabled and when it is +disabled. + +(fn &optional ARG)" t) +(autoload 'denote-add-links "denote" "\ +Insert links to all files whose file names match REGEXP. +Use this command to reference multiple files at once. Particularly +useful for the creation of metanotes (read the manual for more on the +matter). + +Optional ID-ONLY has the same meaning as in `denote-link': it +inserts links with just the identifier. + +(fn REGEXP &optional ID-ONLY)" t) +(autoload 'denote-link-to-file-with-contents "denote" "\ +Link to a file whose contents match QUERY. +This is similar to `denote-link', except that the file prompt is limited +to files matching QUERY. Optional ID-ONLY has the same meaning as in +`denote-link'. + +(fn QUERY &optional ID-ONLY)" t) +(autoload 'denote-link-to-all-files-with-contents "denote" "\ +Link to all files whose contents match QUERY. +This is similar to `denote-add-links', except it searches inside file +contents, not file names. Optional ID-ONLY has the same meaning as in +`denote-link' and `denote-add-links'. + +(fn QUERY &optional ID-ONLY)" t) +(autoload 'denote-link-dired-marked-notes "denote" "\ +Insert Dired marked FILES as links in BUFFER. + +FILES conform with the Denote file-naming scheme, such that they can be +linked to using the `denote:' link type. + +The BUFFER is one which visits a Denote note file. If there are +multiple BUFFER candidates in buffers, prompt with completion for +one among them. If there is none, throw an error. + +With optional ID-ONLY as a prefix argument, insert links with +just the identifier (same principle as with `denote-link'). + +This command is meant to be used from a Dired buffer. + +(fn FILES BUFFER &optional ID-ONLY)" '(dired-mode)) +(defvar denote-menu-bar-mode t "\ +Non-nil if Denote-Menu-Bar mode is enabled. +See the `denote-menu-bar-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `denote-menu-bar-mode'.") +(custom-autoload 'denote-menu-bar-mode "denote" nil) +(autoload 'denote-menu-bar-mode "denote" "\ +Show Denote menu bar. + +This is a global minor mode. If called interactively, toggle the +`Denote-Menu-Bar mode' mode. If the prefix argument is positive, enable +the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable the +mode if ARG is nil, omitted, or is a positive number. Disable the mode +if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `(default-value \\='denote-menu-bar-mode)'. + +The mode's hook is called both when the mode is enabled and when it is +disabled. + +(fn &optional ARG)" t) +(autoload 'denote-link-ol-follow "denote" "\ +Find file of type `denote:' matching LINK. +LINK is the identifier of the note, optionally followed by a file search +option akin to that of standard Org `file:' link types. Read Info +node `(org) Query Options'. + +If LINK is not an identifier, then it is not pointing to a file but to a +query of file contents or file names (see the commands +`denote-query-contents-link' and `denote-query-filenames-link'). + +Uses the function `denote-directory' to establish the path to the file. + +(fn LINK)") +(autoload 'denote-link-ol-complete "denote" "\ +Like `denote-link' but for Org integration. +This lets the user complete a link through the `org-insert-link' +interface by first selecting the `denote:' hyperlink type.") +(autoload 'denote-link-ol-store "denote" "\ +Handler for `org-store-link' adding support for denote: links. +Optional INTERACTIVE? is used by `org-store-link'. + +Also see the user option `denote-org-store-link-to-heading'. + +(fn &optional INTERACTIVE?)") +(autoload 'denote-link-ol-export "denote" "\ +Export a `denote:' link from Org files. +The LINK, DESCRIPTION, and FORMAT are handled by the export +backend. + +(fn LINK DESCRIPTION FORMAT)") +(eval-after-load 'org `(funcall ',(lambda nil (with-no-warnings (org-link-set-parameters "denote" :follow #'denote-link-ol-follow :face #'denote-get-link-face :help-echo #'denote-link-ol-help-echo :complete #'denote-link-ol-complete :store #'denote-link-ol-store :export #'denote-link-ol-export))))) +(autoload 'denote-org-capture "denote" "\ +Create new note through `org-capture-templates'. +Use this as a function that returns the path to the new file. +The file is populated with Denote's front matter. It can then be +expanded with the usual specifiers or strings that +`org-capture-templates' supports. + +This function obeys `denote-prompts', but it ignores `file-type', +if present: it always sets the Org file extension for the created +note to ensure that the capture process works as intended, +especially for the desired output of the +`denote-org-capture-specifiers' (which can include arbitrary +text). + +Consult the manual for template samples.") +(autoload 'denote-org-capture-with-prompts "denote" "\ +Like `denote-org-capture' but with optional prompt parameters. + +When called without arguments, do not prompt for anything. Just +return the front matter with title and keyword fields empty and +the date and identifier fields specified. Also make the file +name consist of only the identifier plus the Org file name +extension. + +Otherwise produce a minibuffer prompt for every non-nil value +that corresponds to the TITLE, KEYWORDS, SUBDIRECTORY, DATE, and +TEMPLATE arguments. The prompts are those used by the standard +`denote' command and all of its utility commands. + +When returning the contents that fill in the Org capture +template, the sequence is as follows: front matter, TEMPLATE, and +then the value of the user option `denote-org-capture-specifiers'. + +Important note: in the case of SUBDIRECTORY actual subdirectories +must exist---Denote does not create them. Same principle for +TEMPLATE as templates must exist and are specified in the user +option `denote-templates'. + +(fn &optional TITLE KEYWORDS SUBDIRECTORY DATE TEMPLATE)") +(defvar denote-rename-buffer-mode nil "\ +Non-nil if Denote-Rename-Buffer mode is enabled. +See the `denote-rename-buffer-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `denote-rename-buffer-mode'.") +(custom-autoload 'denote-rename-buffer-mode "denote" nil) +(autoload 'denote-rename-buffer-mode "denote" "\ +Automatically rename Denote buffers to be easier to read. + +A buffer is renamed upon visiting the underlying file. This +means that existing buffers are not renamed until they are +visited again in a new buffer (files are visited with the command +`find-file' or related). + +This is a global minor mode. If called interactively, toggle the +`Denote-Rename-Buffer mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable the +mode if ARG is nil, omitted, or is a positive number. Disable the mode +if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `(default-value \\='denote-rename-buffer-mode)'. + +The mode's hook is called both when the mode is enabled and when it is +disabled. + +(fn &optional ARG)" t) +(register-definition-prefixes "denote" '("denote-")) + +;;; End of scraped data + +(provide 'denote-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; denote-autoloads.el ends here blob - /dev/null blob + d9e18a70a8b0eedb75f28e1fe6317142707c3127 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/denote-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from mode: lisp-data; .el -*- denoteno-byte-compile: t -*- +(define-package "denote" "4.0.0" "Simple notes with an efficient file-naming scheme" '((emacs "28.1")) :commit "c98c74ec74fb6f0c894c5bf730d092d716e55b84" :authors '(("Protesilaos Stavrou" . "info@protesilaos.com")) :maintainer '("Protesilaos Stavrou" . "info@protesilaos.com") :url "https://github.com/protesilaos/denote") blob - /dev/null blob + e0677608ee1d1c1cd666f866a131ce348564995e (mode 644) --- /dev/null +++ elpa/denote-4.0.0/denote.el @@ -0,0 +1,6478 @@ +;;; denote.el --- Simple notes with an efficient file-naming scheme -*- lexical-binding: t -*- + +;; Copyright (C) 2022-2025 Free Software Foundation, Inc. + +;; Author: Protesilaos Stavrou +;; Maintainer: Protesilaos Stavrou +;; URL: https://github.com/protesilaos/denote +;; Version: 4.0.0 +;; Package-Requires: ((emacs "28.1")) + +;; This file is NOT part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Denote aims to be a simple-to-use, focused-in-scope, and effective +;; note-taking and file-naming tool for Emacs. +;; +;; Denote is based on the idea that files should follow a predictable +;; and descriptive file-naming scheme. The file name must offer a +;; clear indication of what the contents are about, without reference +;; to any other metadata. Denote basically streamlines the creation +;; of such files or file names while providing facilities to link +;; between them (where those files are editable). +;; +;; Denote's file-naming scheme is not limited to "notes". It can be used +;; for all types of file, including those that are not editable in Emacs, +;; such as videos. Naming files in a consistent way makes their +;; filtering and retrieval considerably easier. Denote provides relevant +;; facilities to rename files, regardless of file type. +;; +;; The manual describes all the technicalities about the file-naming +;; scheme, points of entry to creating new notes, commands to check +;; links between notes, and more: ;; . +;; If you have the info manual available, evaluate: +;; +;; (info "(denote) Top") +;; +;; What follows is a general overview of its core core design +;; principles (again: please read the manual for the technicalities): +;; +;; * Predictability :: File names must follow a consistent and +;; descriptive naming convention (see the manual's "The file-naming +;; scheme"). The file name alone should offer a clear indication of +;; what the contents are, without reference to any other metadatum. +;; This convention is not specific to note-taking, as it is pertinent +;; to any form of file that is part of the user's long-term storage +;; (see the manual's "Renaming files"). +;; +;; * Composability :: Be a good Emacs citizen, by integrating with other +;; packages or built-in functionality instead of re-inventing +;; functions such as for filtering or greping. The author of Denote +;; (Protesilaos, aka "Prot") writes ordinary notes in plain text +;; (`.txt'), switching on demand to an Org file only when its expanded +;; set of functionality is required for the task at hand (see the +;; manual's "Points of entry"). +;; +;; * Portability :: Notes are plain text and should remain portable. +;; The way Denote writes file names, the front matter it includes in +;; the note's header, and the links it establishes must all be +;; adequately usable with standard Unix tools. No need for a databse +;; or some specialised software. As Denote develops and this manual +;; is fully fleshed out, there will be concrete examples on how to do +;; the Denote-equivalent on the command-line. +;; +;; * Flexibility :: Do not assume the user's preference for a +;; note-taking methodology. Denote is conceptually similar to the +;; Zettelkasten Method, which you can learn more about in this +;; detailed introduction: . +;; Notes are atomic (one file per note) and have a unique identifier. +;; However, Denote does not enforce a particular methodology for +;; knowledge management, such as a restricted vocabulary or mutually +;; exclusive sets of keywords. Denote also does not check if the user +;; writes thematically atomic notes. It is up to the user to apply +;; the requisite rigor and/or creativity in pursuit of their preferred +;; workflow (see the manual's "Writing metanotes"). +;; +;; * Hackability :: Denote's code base consists of small and reusable +;; functions. They all have documentation strings. The idea is to +;; make it easier for users of varying levels of expertise to +;; understand what is going on and make surgical interventions where +;; necessary (e.g. to tweak some formatting). In this manual, we +;; provide concrete examples on such user-level configurations (see +;; the manual's "Keep a journal or diary"). +;; +;; Now the important part... "Denote" is the familiar word, though it +;; also is a play on the "note" concept. Plus, we can come up with +;; acronyms, recursive or otherwise, of increasingly dubious utility +;; like: +;; +;; + Don't Ever Note Only The Epiphenomenal +;; + Denote Everything Neatly; Omit The Excesses +;; +;; But we'll let you get back to work. Don't Eschew or Neglect your +;; Obligations, Tasks, and Engagements. + +;;; Code: + +(require 'seq) +(require 'xref) +(require 'dired) +(eval-when-compile (require 'subr-x)) + +(defgroup denote () + "Simple notes with an efficient file-naming scheme." + :group 'files + :link '(info-link "(denote) Top") + :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) + +;;;; User options + +;; About the autoload: (info "(elisp) File Local Variables") + +;;;###autoload (put 'denote-directory 'safe-local-variable (lambda (val) (or (stringp val) (eq val 'local) (eq val 'default-directory)))) +(defcustom denote-directory (expand-file-name "~/Documents/notes/") + "Directory for storing personal notes. + +If you intend to reference this variable in Lisp, consider using +the function `denote-directory' instead." + :group 'denote + :safe (lambda (val) (or (stringp val) (eq val 'local) (eq val 'default-directory))) + :package-version '(denote . "2.0.0") + :link '(info-link "(denote) Maintain separate directories for notes") + :type 'directory) + +(define-obsolete-variable-alias 'denote-save-buffer-after-creation 'denote-save-buffers "3.0.0") + +(defcustom denote-save-buffers nil + "Control whether commands that handle new notes save their buffer outright. + +The default behaviour of commands such as `denote' (or related) +is to not save the buffer they create. This gives the user the +chance to review the text before writing it to a file. The user +may choose to delete the unsaved buffer, thus not creating a new +note. + +This option also applies to notes affected by the renaming +commands (`denote-rename-file' and related). + +If this user option is set to a non-nil value, such buffers are +saved automatically. The assumption is that the user who opts in +to this feature is familiar with the `denote-rename-file' +operation (or related) and knows it is reliable. Data loss may +occur if the file is modified externally. + +Also see `denote-kill-buffers'." + :group 'denote + :package-version '(denote . "3.0.0") + :type 'boolean) + +(defcustom denote-kill-buffers nil + "Control whether creation or renaming commands kill their buffer. + +The default behaviour of creation or renaming commands such as +`denote' or `denote-rename-file' is to not kill the buffer they +create or modify at the end of their operation. + +If this user option is nil (the default), buffers affected by a +creation or renaming command are not automatically killed. + +If set to `on-creation', new notes are automatically killed. + +If set to `on-rename', renamed notes are automatically killed. + +If set to t, new and renamed notes are killed. + +If a buffer is killed, it is also saved, as if `denote-save-buffers' +were t. See its documentation. + +In all cases, if the buffer already existed before the Denote operation +it is NOT automatically killed." + :group 'denote + :package-version '(denote . "3.1.0") + :type '(choice + (const :tag "Do not kill buffers" nil) + (const :tag "Kill after creation" on-creation) + (const :tag "Kill after rename" on-rename) + (const :tag "Kill after creation and rename" t))) + +;;;###autoload (put 'denote-known-keywords 'safe-local-variable #'listp) +(defcustom denote-known-keywords + '("emacs" "philosophy" "politics" "economics") + "List of strings with predefined keywords for `denote'. +Also see user options: `denote-infer-keywords', +`denote-sort-keywords', `denote-file-name-slug-functions'." + :group 'denote + :safe #'listp + :package-version '(denote . "0.1.0") + :type '(repeat string)) + +;;;###autoload (put 'denote-infer-keywords 'safe-local-variable (lambda (val) (or val (null val)))) +(defcustom denote-infer-keywords t + "Whether to infer keywords from existing notes' file names. + +When non-nil, search the file names of existing notes in the +variable `denote-directory' for their keyword field and extract +the entries as \"inferred keywords\". These are combined with +`denote-known-keywords' and are presented as completion +candidates while using `denote' and related commands +interactively. + +If nil, refrain from inferring keywords. The aforementioned +completion prompt only shows the `denote-known-keywords'. Use +this if you want to enforce a restricted vocabulary. + +The user option `denote-excluded-keywords-regexp' can be used to +exclude keywords that match a regular expression. + +Inferred keywords are specific to the value of the variable +`denote-directory'. If a silo with a local value is used, as +explained in that variable's doc string, the inferred keywords +are specific to the given silo. + +For advanced Lisp usage, the function `denote-keywords' returns +the appropriate list of strings." + :group 'denote + :safe (lambda (val) (or val (null val))) + :package-version '(denote . "0.1.0") + :type 'boolean) + +(defcustom denote-prompts '(title keywords) + "Specify the prompts followed by relevant Denote commands. + +Commands that prompt for user input to construct a Denote file name +include, but are not limited to: `denote', `denote-signature', +`denote-type', `denote-date', `denote-subdirectory', +`denote-rename-file', `denote-dired-rename-files'. + +The value of this user option is a list of symbols, which includes any +of the following: + +- `title': Prompt for the title of the new note. + +- `keywords': Prompts with completion for the keywords of the new note. + Available candidates are those specified in the user option + `denote-known-keywords'. If the user option `denote-infer-keywords' + is non-nil, keywords in existing note file names are included in the + list of candidates. The `keywords' prompt uses `completing-read-multiple', + meaning that it can accept multiple keywords separated by a comma (or + whatever the value of `crm-separator' is). + +- `file-type': Prompts with completion for the file type of the new + note. Available candidates are those specified in the user option + `denote-file-type'. Without this prompt, `denote' uses the value of + the variable `denote-file-type'. + +- `subdirectory': Prompts with completion for a subdirectory in which to + create the note. Available candidates are the value of the user + option `denote-directory' and all of its subdirectories. Any + subdirectory must already exist: Denote will not create it. + +- `date': Prompts for the date of the new note. It will expect an input + like 2022-06-16 or a date plus time: 2022-06-16 14:30. Without the + `date' prompt, the `denote' command uses the `current-time'. (To + leverage the more sophisticated Org method, see the + `denote-date-prompt-use-org-read-date'.) + +- `template': Prompts for a KEY among `denote-templates'. The value of + that KEY is used to populate the new note with content, which is added + after the front matter. + +- `signature': Prompts for an arbitrary string that can be used for any + kind of workflow, such as a special tag to label the part1 and part2 + of a large file that is split in half, or to add special contexts like + home and work, or even priorities like a, b, c. One other use-case is + to implement a sequencing scheme that makes notes have hierarchical + relationships. This is handled by our optional extension + denote-sequence.el, which is part of the denote package (read the + manual). + +The prompts occur in the given order. + +If the value of this user option is nil, no prompts are used. The +resulting file name will consist of an identifier (i.e. the date and +time) and a supported file type extension (per the variable +`denote-file-type'). + +Recall that Denote's standard file-naming scheme is defined as +follows (read the manual for the technicalities): + + DATE--TITLE__KEYWORDS.EXT + +Depending on the inclusion of the `title', `keywords', and +`signature' prompts, file names will be any of those +permutations: + + DATE.EXT + DATE--TITLE.EXT + DATE__KEYWORDS.EXT + DATE==SIGNATURE.EXT + DATE==SIGNATURE--TITLE.EXT + DATE==SIGNATURE--TITLE__KEYWORDS.EXT + DATE==SIGNATURE__KEYWORDS.EXT + +When in doubt, always include the `title' and `keywords' +prompts (the default style). + +Finally, this user option only affects the interactive use of the +`denote' or other relevant commands (advanced users can call it from +Lisp). In Lisp usage, the behaviour is always what the caller +specifies, based on the supplied arguments. + +Also see `denote-history-completion-in-prompts'. + +To change the order of the file name components, refer to +`denote-file-name-components-order'." + :group 'denote + :package-version '(denote . "2.3.0") + :link '(info-link "(denote) The denote-prompts option") + :type '(radio (const :tag "Use no prompts" nil) + (set :tag "Available prompts" :greedy t + (const :tag "Title" title) + (const :tag "Keywords" keywords) + (const :tag "Date" date) + (const :tag "File type extension" file-type) + (const :tag "Subdirectory" subdirectory) + (const :tag "Template" template) + (const :tag "Signature" signature)))) + +(defcustom denote-file-name-components-order '(identifier signature title keywords) + "Specify the order of the file name components. + +The value is a list of the following symbols: + +- `identifier': This is the combination of the date and time. When it + is the first on the list, it looks like \"20240519T073456\" and does + not have a component separator of its own due its unambiguous format. + When it is placed anywhere else in the file name, it is prefixed with + \"@@\", so it looks like \"@@20240519T073456\". + +- `signature': This is an arbitrary string that can be used to qualify + the file in some way, according to the user's methodology (e.g. to add + a sequence to notes). The string is always prefixed with the \"==\" + to remain unambiguous. + +- `title': This is an arbitrary string which describes the file. It is + always prefixed with \"--\" to be unambiguous. + +- `keywords': This is a series of one or more words that succinctly + group the file. Multiple keywords are separated by an underscore + prefixed to each of them. The file name component is always prefixed + with \"__\". + +All four symbols must appear exactly once. Duplicates are ignored. Any +missing symbol is added automatically. + +Some examples: + + (setq denote-file-name-components-order + \\='(identifier signature title keywords)) + => 20240519T07345==hello--this-is-the-title__denote_testing.org + + (setq denote-file-name-components-order + \\='(signature identifier title keywords)) + => ==hello@@20240519T07345--this-is-the-title__denote_testing.org + + (setq denote-file-name-components-order + \\='(title signature identifier keywords)) + => --this-is-the-title==hello@@20240519T07345__denote_testing.org + + (setq denote-file-name-components-order + \\='(keywords title signature identifier)) + => __denote_testing--this-is-the-title==hello@@20240519T07345.org + +Also see the user option `denote-prompts', which affects which +components are actually used in the order specified herein. + +Before deciding on this, please consider the longer-term implications +of file names with varying patterns. Consistency makes things +predictable and thus easier to find. So pick one order and never touch +it again. When in doubt, leave the default file-naming scheme as-is. + +This user option should only be used to build a file name. Custom code +should not have behaviors that depend on its value. The reason is that +its value can change over time and Denote should be able to handle past +and current notes." + :group 'denote + :package-version '(denote . "3.0.0") + ;; FIXME 2024-05-19: This technically works to display the user + ;; option in the Custom buffer and to show its current value, though + ;; it does not allow the user to modify it graphically: they have to + ;; switch to the Lisp expression. Find a way to present an + ;; interface that lets the user reorder those elements. + ;; + ;; Still, making this a defcustom helps with discoverability, as + ;; well as with the use of `setopt' and related. + :type '(list + (const :tag "Identifier component (date and time)" identifier) + (const :tag "File signature (text to qualify a file)" signature) + (const :tag "The title of the file" title) + (const :tag "Keywords of the file" keywords))) + +(defcustom denote-front-matter-components-present-even-if-empty-value '(title keywords date identifier) + "The components that are always present in front matter even when empty. + +Components are `title', `keywords', `signature', `date', `identifier'. + +Note that even though a component may be listed in this variable, it +will not be present in the front matter if the corresponding line is not +in the front matter template." + :group 'denote + :package-version '(denote . "4.0.0") + :type '(list + (const :tag "Title" title) + (const :tag "Keywords" keywords) + (const :tag "Signature" signature) + (const :tag "Date" date) + (const :tag "Identifier" identifier))) + +(defcustom denote-sort-keywords t + "Whether to sort keywords in new files. + +When non-nil, the keywords of `denote' are sorted with +`string-collate-lessp' regardless of the order they were inserted at the +minibuffer prompt. + +If nil, show the keywords in their given order." + :group 'denote + :package-version '(denote . "0.1.0") + :type 'boolean) + +(defcustom denote-file-type nil + "The file type extension for new notes. + +By default (a nil value), the file type is that of Org mode. +Though the `org' symbol can be specified for the same effect. + +When the value is the symbol `markdown-yaml', the file type is +that of Markdown mode and the front matter uses YAML notation. +Similarly, `markdown-toml' is Markdown but has TOML syntax in the +front matter. + +When the value is `text', the file type is that of Text mode. + +Any other non-nil value is the same as the default. + +NOTE: Expert users can change the supported file-types by editing +the value of `denote-file-types'. That variable, which is not a +user option, controls the behaviour of all file-type-aware +functions (creating notes, renaming them, inserting front matter, +formatting a link, etc.). Consult its documentation for the +technicalities." + :type '(choice + (const :tag "Unspecified (defaults to Org)" nil) + (const :tag "Org mode (default)" org) + (const :tag "Markdown (YAML front matter)" markdown-yaml) + (const :tag "Markdown (TOML front matter)" markdown-toml) + (const :tag "Plain text" text)) + :package-version '(denote . "0.6.0") + :group 'denote) + +(defcustom denote-date-format nil + "Date format in the front matter (file header) of new notes. + +When nil (the default value), use a file-type-specific +format (also check the user option `denote-file-type'): + +- For Org, an inactive timestamp is used, such as [2022-06-30 Wed + 15:31]. + +- For Markdown, the RFC3339 standard is applied: + 2022-06-30T15:48:00+03:00. + +- For plain text, the format is that of ISO 8601: 2022-06-30. + +If the value is a string, ignore the above and use it instead. +The string must include format specifiers for the date. These +are described in the doc string of `format-time-string'." + :type '(choice + (const :tag "Use appropiate format for each file type" nil) + (string :tag "Custom format for `format-time-string'")) + :package-version '(denote . "0.2.0") + :group 'denote) + +(defcustom denote-date-prompt-use-org-read-date nil + "Whether to use `org-read-date' in date prompts. + +If non-nil, use `org-read-date'. If nil, input the date as a +string, as described in `denote'. + +This option is relevant when `denote-prompts' includes a `date' +and/or when the user invokes the command `denote-date'." + :group 'denote + :package-version '(denote . "0.6.0") + :type 'boolean) + +(defcustom denote-org-store-link-to-heading nil + "Determine whether `org-store-link' links to the current Org heading. + +[ Remember that what `org-store-link' does is merely collect a link. To + actually insert it, use the command `org-insert-link'. Note that + `org-capture' uses `org-store-link' internally when it needs to store + a link. ] + +When the value is nil, the Denote handler for `org-store-link' produces +links only to the current file (by using the file's identifier). For +example: + + [[denote:20240118T060608][Some test]] + +If the value is `context', the link consists of the file's identifier +and the text of the current heading, like this: + + [[denote:20240118T060608::*Heading text][Some test::Heading text]]. + +However, if there already exists a CUSTOM_ID property for the current +heading, this is always given priority and is used instead of the +context. + +If the value is `id' or, for backward-compatibility, any other non-nil +value, then Denote will use the standard Org mechanism of the CUSTOM_ID +property to create a unique link to the heading. If the heading does +not have a CUSTOM_ID, it creates it and includes it in its PROPERTIES +drawer. If a CUSTOM_ID exists, it takes it as-is. The result is like +this: + + [[denote:20240118T060608::#h:eed0fb8e-4cc7-478f][Some test::Heading text]] + +The value of the CUSTOM_ID is determined by the Org user option +`org-id-method'. The sample shown above uses the default UUID +infrastructure (though I deleted a few characters to not get +complaints from the byte compiler about long lines in the doc +string...). + +Note that this option does not affect how Org behaves with regard to +`org-id-link-to-org-use-id'. If that user option is set to create ID +properties, then those will be created by Org even if the Denote link +handler will take care to not use/store the ID value. Concretely, users +who never want ID properties under their headings should keep +`org-id-link-to-org-use-id' in its nil value. + +Context links are easier to break than those with a CUSTOM_ID in cases +where either the heading text changes or there is another heading that +matches that text. The potential advantage of context links is that +they do not require a PROPERTIES drawer. + +When visiting a link to a heading, Org opens the Denote file and then +navigates to that heading. + +[ This feature only works in Org mode files, as other file types + do not have a linking mechanism that handles unique identifiers + for headings or other patterns to jump to. If `org-store-link' + is invoked in one such file, it captures only the Denote + identifier of the file, even if this user option is set to a + non-nil value. ]" + :group 'denote + :package-version '(denote . "4.0.0") + :type '(choice (const :tag "No link to heading (default)" nil) + (const :tag "Link to the context" context) + (const :tag "Link wtih CUSTOM_ID, creating it if needed" id))) + +(defcustom denote-templates nil + "Alist of content templates for new notes. +A template is arbitrary text that Denote will add to a newly +created note right below the front matter. + +Templates are expressed as a (KEY . VALUE) association. + +- The KEY is the name which identifies the template. It is an + arbitrary symbol, such as `report', `memo', `statement'. + +- The VALUE is either a string or the symbol of a function. + + - If it is a string, it is ordinary text that Denote will insert + as-is. It can contain newline characters to add spacing. The + manual of Denote contains examples on how to use the `concat' + function, beside writing a generic string. + + - If it is a function, it is called without arguments and is expected + to return a string. Denote will call the function and insert the + result in the buffer. + +The user can choose a template either by invoking the command +`denote-template' or by changing the user option `denote-prompts' +to always prompt for a template when calling the `denote' +command." + :type '(alist :key-type symbol :value-type (choice string function)) + :package-version '(denote . "3.1.0") + :link '(info-link "(denote) The denote-templates option") + :group 'denote) + +(make-obsolete-variable 'denote-rename-no-confirm 'denote-rename-confirmations "3.0.0") + +(defcustom denote-rename-confirmations '(rewrite-front-matter modify-file-name) + "Make renaming commands prompt for confirmations. + +This affects the behaviour of renaming commands. The value is either +nil, in which case no confirmation is ever requested, or a list of +symbols among the following: + +- `modify-file-name' means that renaming commands will ask for + confirmation before modifying the file name. + +- `rewrite-front-matter' means that renaming commands will ask for + confirmation before rewritting the front matter. + +- `add-front-matter' means that renaming commands will ask for + confirmation before adding new front matter to the file. + +The default behaviour of the `denote-rename-file' command (and others +like it) is to ask for an affirmative answer as a final step before +changing the file name and, where relevant, inserting or updating the +corresponding front matter. + +Specialized commands that build on top of `denote-rename-file' (or +related) may internally bind this user option to a non-nil value in +order to perform their operation (e.g. `denote-dired-rename-files' goes +through each marked Dired file, prompting for the information to use, +but carries out the renaming without asking for confirmation)." + :group 'denote + :type '(radio (const :tag "Disable all confirmations" nil) + (set :tag "Available confirmations" :greedy t + (const :tag "Add front matter" add-front-matter) + (const :tag "Rewrite front matter" rewrite-front-matter) + (const :tag "Modify file name" modify-file-name)))) + +(defcustom denote-excluded-directories-regexp nil + "Regular expression of directories to exclude from all operations. +Omit matching directories from file prompts and also exclude them +from all functions that check the contents of the variable +`denote-directory'. The regexp needs to match only the name of +the directory, not its full path. + +File prompts are used by several commands, such as `denote-link' +and `denote-subdirectory'. + +Functions that check for files include `denote-directory-files' +and `denote-directory-subdirectories'. + +The match is performed with `string-match-p'." + :group 'denote + :package-version '(denote . "1.2.0") + :type 'string) + +(defcustom denote-excluded-keywords-regexp nil + "Regular expression of keywords to not infer. +Keywords are inferred from file names and provided at relevant +prompts as completion candidates when the user option +`denote-infer-keywords' is non-nil. + +The match is performed with `string-match-p'." + :group 'denote + :package-version '(denote . "1.2.0") + :type 'string) + +(defcustom denote-excluded-files-regexp nil + "Regular expression of files that are excluded from Denote file prompts. +Files are provided for completion when using commands like `denote-link' +and `denote-open-or-create'. + +The match is performed with `string-match-p' on the full file path." + :group 'denote + :package-version '(denote . "3.0.0") + :type 'string) + +(defcustom denote-after-new-note-hook nil + "Normal hook that runs after the `denote' command. +This also covers all convenience functions that call `denote' +internally, such as `denote-signature' and `denote-type' (check +the default value of the user option `denote-commands-for-new-notes')." + :group 'denote + :package-version '(denote . "2.1.0") + :link '(info-link "(denote) Standard note creation") + :type 'hook) + +(defcustom denote-after-rename-file-hook nil + "Normal hook called after a succesful Denote rename operation. +This affects the behaviour of the commands `denote-rename-file', +`denote-dired-rename-files', `denote-rename-file-using-front-matter', +`denote-dired-rename-marked-files-with-keywords', +`denote-dired-rename-marked-files-using-front-matter', +`denote-keywords-add', `denote-keywords-remove', and any other +command that builds on top of them." + :group 'denote + :package-version '(denote . "2.3.0") + :link '(info-link "(denote) Renaming files") + :type 'hook) + +(defcustom denote-region-after-new-note-functions nil + "Abnormal hook called after `denote-region'. +Functions in this hook are called with two arguments, +representing the beginning and end buffer positions of the region +that was inserted in the new note. These are called only if +`denote-region' is invoked while a region is active. + +A common use-case is to call `org-insert-structure-template' +after a region is inserted. This case does not actually require +the aforementioned arguments, in which case the function can +simply declare them as ignored by prefixing the argument names +with an underscore. For example, the following will prompt for a +structure template as soon as `denote-region' is done: + + (defun my-denote-region-org-structure-template (_beg _end) + (when (derived-mode-p \\='org-mode) + (activate-mark) + (call-interactively \\='org-insert-structure-template))) + + (add-hook \\='denote-region-after-new-note-functions + #\\='my-denote-region-org-structure-template)" + :group 'denote + :package-version '(denote . "2.1.0") + :link '(info-link "(denote) Create a note with the region's contents") + :type 'hook) + +(defvar denote-prompts-with-history-as-completion + '(denote-title-prompt denote-signature-prompt denote-files-matching-regexp-prompt denote-query-link-prompt) + "Prompts that conditionally perform completion against their history. + +These are minibuffer prompts that ordinarily accept a free form string +input, as opposed to matching against a predefined set. + +These prompts can optionally perform completion against their own +minibuffer history when the user option `denote-history-completion-in-prompts' +is set to a non-nil value.") + +(defcustom denote-history-completion-in-prompts t + "Toggle history completion in all `denote-prompts-with-history-as-completion'. + +When this user option is set to a non-nil value, use minibuffer history +entries as completion candidates in `denote-prompts-with-history-as-completion'. +Those will show previous inputs from their respective history as +possible values to select, either to (i) re-insert them verbatim or (ii) +with the intent to edit further (depending on the minibuffer user +interface, one can select a candidate with TAB without exiting the +minibuffer, as opposed to what RET normally does by selecting and +exiting). + +When this user option is set to a nil value, all of the +`denote-prompts-with-history-as-completion' do not use minibuffer +completion: they just prompt for a string of characters. Their +history is still available through all the standard ways of retrieving +minibuffer history, such as with the command `previous-history-element'. + +History completion still allows arbitrary values to be provided as +input: they do not have to match the available minibuffer completion +candidates. + +Note that some prompts, like `denote-keywords-prompt', always use +minibuffer completion, due to the specifics of their data. + +[ Consider enabling the built-in `savehist-mode' to persist minibuffer + histories between sessions.] + +Also see `denote-prompts'." + :type 'boolean + :package-version '(denote . "2.3.0") + :group 'denote) + +(defcustom denote-commands-for-new-notes + '(denote + denote-date + denote-subdirectory + denote-template + denote-type + denote-signature) + "List of commands for `denote-command-prompt' that create a new note. +These are used by commands such as `denote-open-or-create-with-command' +and `denote-link-after-creating-with-command'." + :group 'denote + :package-version '(denote . "2.1.0") + :link '(info-link "(denote) Choose which commands to prompt for") + :type '(repeat symbol)) + +(defcustom denote-file-name-slug-functions + '((title . denote-sluggify-title) + (signature . denote-sluggify-signature) + (keyword . denote-sluggify-keyword)) + "Specify the method Denote uses to format the components of the file name. + +The value is an alist where each element is a cons cell of the +form (COMPONENT . METHOD). + +- The COMPONENT is an unquoted symbol among `title', `signature', + `keyword' (notice the absence of `s', see below), which + refers to the corresponding component of the file name. + +- The METHOD is the function to be used to format the given + component. This function should take a string as its parameter + and return the string formatted for the file name. In the case + of the `keyword' component, the function receives a SINGLE + string representing a single keyword and return it formatted + for the file name. Joining the keywords together is handled by + Denote. + +Note that the `keyword' function is also applied to the keywords +of the front matter. + +By default, if a function is not specified for a component, we +use `denote-sluggify-title', `denote-sluggify-keyword' and +`denote-sluggify-signature'. + +Remember that deviating from the default file-naming scheme of Denote +will make things harder to search in the future, as files can/will have +permutations that create uncertainty. The sluggification scheme and +concomitant restrictions we impose by default are there for a very good +reason: they are the distillation of years of experience. Here we give +you what you wish, but bear in mind it may not be what you need. You +have been warned." + :group 'denote + :package-version '(denote . "2.3.0") + :link '(info-link "(denote) User-defined sluggification of file name components") + :type '(alist :key (choice (const title) + (const signature) + (const keyword)) + :value function)) + +(define-obsolete-variable-alias + 'denote-link-button-action + 'denote-open-link-function + "4.0.0") + +(defcustom denote-open-link-function #'find-file-other-window + "Function to find the file of a Denote link. + +The default value is `find-file-other-window', with `find-file' because +another common option. Users can provide a custom function which +behaves like the other two. + +This is used in all non-Org buffers that have a link created by Denote. +Org has its own mechanism, which you can learn more about by reading the +documentation of the `org-open-at-point' command." + :group 'denote + :type '(choice (function :tag "Other window" find-file-other-window) + (function :tag "Current window" find-file) + (function :tag "Custom function")) + :package-version '(denote . "4.0.0")) + +(define-obsolete-variable-alias + 'denote-link-description-function + 'denote-link-description-format + "4.0.0") + +(defcustom denote-link-description-format #'denote-link-description-with-signature-and-title + "The format of a link description text. +This determines how `denote-link' and related functions create a link +description by default. + +The value can be either a function or a string. If it is a function, it +is called with one argument, the file, and should return a string +representing the link description. + +The default is a function that returns the active region or the title of +the note (with the signature if present). + +If the value is a string, it treats specially the following specifiers: + +- The %t is the Denote TITLE in the front matter or the file name. +- The %T is the Denote TITLE in the file name. +- The %i is the Denote IDENTIFIER of the file. +- The %I is the identifier converted to DAYNAME, DAYNUM MONTHNUM YEAR. +- The %d is the same as %i (DATE mnemonic). +- The %D is a \"do what I mean\" which behaves the same as %t and if + that returns nothing, it falls back to %I, then %i. +- The %s is the Denote SIGNATURE of the file. +- The %k is the Denote KEYWORDS of the file. +- The %% is a literal percent sign. + +In addition, the following flags are available for each of the specifiers: + +- 0 :: Pad to the width, if given, with zeros instead of spaces. +- - :: Pad to the width, if given, on the right instead of the left. +- < :: Truncate to the width and precision, if given, on the left. +- > :: Truncate to the width and precision, if given, on the right. +- ^ :: Convert to upper case. +- _ :: Convert to lower case. + +When combined all together, the above are written thus: + + %SPECIFIER-CHARACTER + +Any other text in the string it taken as-is. Users may want, for +example, to include some text that makes Denote links stand out, such as +a [D] prefix. + +If the region is active, its text is used as the link's description." + :type '(choice + (string :tag "String with treats format specifiers specially") + (function :tag "Custom function like `denote-link-description-with-signature-and-title'")) + :package-version '(denote . "4.0.0") + :group 'denote) + +;;;; Main variables + +;; For character classes, evaluate: (info "(elisp) Char Classes") + +(defconst denote-id-format "%Y%m%dT%H%M%S" + "Format of ID prefix of a note's filename. +The note's ID is derived from the date and time of its creation.") + +(defconst denote-id-regexp "\\([0-9]\\{8\\}\\)\\(T[0-9]\\{6\\}\\)" + "Regular expression to match `denote-id-format'.") + +(defconst denote-signature-regexp "==\\([^.]*?\\)\\(==.*\\|--.*\\|__.*\\|@@.*\\|\\..*\\)*$" + "Regular expression to match the SIGNATURE field in a file name.") + +(defconst denote-title-regexp "--\\([^.]*?\\)\\(==.*\\|__.*\\|@@.*\\|\\..*\\)*$" + "Regular expression to match the TITLE field in a file name.") + +(defconst denote-keywords-regexp "__\\([^.]*?\\)\\(==.*\\|--.*\\|__.*\\|@@.*\\|\\..*\\)*$" + "Regular expression to match the KEYWORDS field in a file name.") + +(make-obsolete-variable + 'denote-excluded-punctuation-extra-regexp + 'denote-file-name-slug-functions + "4.0.0") + +;;;; File helper functions + +(defun denote--completion-table (category candidates) + "Pass appropriate metadata CATEGORY to completion CANDIDATES." + (lambda (string pred action) + (if (eq action 'metadata) + `(metadata (category . ,category)) + (complete-with-action action candidates string pred)))) + +(defun denote--completion-table-no-sort (category candidates) + "Pass appropriate metadata CATEGORY to completion CANDIDATES. +Like `denote--completion-table' but also disable sorting." + (lambda (string pred action) + (if (eq action 'metadata) + `(metadata (category . ,category) + (display-sort-function . ,#'identity)) + (complete-with-action action candidates string pred)))) + +(defun denote--default-directory-is-silo-p () + "Return path to silo if `default-directory' is a silo." + (when-let* ((dir-locals (dir-locals-find-file default-directory)) + ((alist-get 'denote-directory dir-local-variables-alist))) + (cond + ((listp dir-locals) + (car dir-locals)) + ((stringp dir-locals) + dir-locals)))) + +(defun denote--make-denote-directory () + "Make the variable `denote-directory' and its parents, if needed." + (when (not (file-directory-p denote-directory)) + (make-directory denote-directory :parents))) + +(defun denote-directory () + "Return path of variable `denote-directory' as a proper directory. +Custom Lisp code can `let' bind the variable `denote-directory' +to override what this function returns." + (if-let* (((or (eq denote-directory 'default-directory) (eq denote-directory 'local))) + (silo-dir (denote--default-directory-is-silo-p))) + (progn + (display-warning + 'denote + "Silo value must be a string; `local' or `default-directory'are obsolete" + :error) + silo-dir) + (let ((denote-directory (file-name-as-directory (expand-file-name denote-directory)))) + (denote--make-denote-directory) + denote-directory))) + +;; TODO: Review and fix the features listed in the docstring below before +;; making this a user option. +(defvar denote-generate-identifier-automatically t + "Make creation and renaming commands automatically create and identifier. + +This applies when a note is created or renamed. The default is to +always create an identifier automatically. + +Valid values are: t, nil, `on-creation', and `on-rename'. + +IMPORTANT: Some features may not work with notes that do not have an +identifier. For example, backlinks do not contain files without an +identifier.") + +;;;;; Sluggification functions + +(defun denote-slug-keep-only-ascii (str) + "Remove all non-ASCII characters from STR and replace them with spaces. +This is useful as a helper function to construct +`denote-file-name-slug-functions'." + (let* ((ascii-range (seq-map + (lambda (character) + (if (and (>= character 33) (<= character 126)) + character + 32)) ; empty space + str)) + (characters (seq-filter #'characterp ascii-range))) + (mapconcat #'string characters))) + +(define-obsolete-function-alias + 'denote--slug-hyphenate + 'denote-slug-hyphenate + "4.0.0") + +(defun denote-slug-hyphenate (str) + "Replace spaces and underscores with hyphens in STR. +Also replace multiple hyphens with a single one and remove any +leading and trailing hyphen." + (replace-regexp-in-string + "^-\\|-$" "" + (replace-regexp-in-string + "-\\{2,\\}" "-" + (replace-regexp-in-string "_\\|\s+" "-" str)))) + +(defun denote-slug-put-equals (str) + "Replace spaces and underscores with equals signs in STR. +Also replace multiple equals signs with a single one and remove +any leading and trailing signs." + (replace-regexp-in-string + "^=\\|=$" "" + (replace-regexp-in-string + "=\\{2,\\}" "=" + (replace-regexp-in-string "_\\|\s+" "=" str)))) + +(defun denote--remove-dot-characters (str) + "Remove dot characters from STR." + (replace-regexp-in-string "\\." "" str)) + +(defun denote--trim-right-token-characters (str component) + "Remove =, -, _ and @ from the end of STR. +The removal is done only if necessary according to COMPONENT." + (if (eq component 'title) + (string-trim-right str "[=@_]+") + (string-trim-right str "[=@_-]+"))) + +(defun denote--replace-consecutive-token-characters (str component) + "Replace consecutive characters with a single one in STR. +Hyphens, underscores, equal signs and at signs are replaced with +a single one in str, if necessary according to COMPONENT." + (let ((str (replace-regexp-in-string + "_\\{2,\\}" "_" + (replace-regexp-in-string + "=\\{2,\\}" "=" + (replace-regexp-in-string + "@\\{2,\\}" "@" str))))) + ;; -- are allowed in titles when the default sluggification is disabled + (if (eq component 'title) + str + (replace-regexp-in-string + "-\\{2,\\}" "-" str)))) + +(defun denote-sluggify (component str) + "Make STR an appropriate slug for file name COMPONENT. + +Apply the function specified in `denote-file-name-slug-function' to +COMPONENT which is one of `title', `signature', `keyword'. If the +resulting string still contains consecutive -, _, =, or @, they are +replaced by a single occurence of the character, if necessary according +to COMPONENT. If COMPONENT is `keyword', remove underscores from STR as +they are used as the keywords separator in file names." + (let* ((slug-function (alist-get component denote-file-name-slug-functions)) + (str-slug (cond ((eq component 'title) + (funcall (or slug-function #'denote-sluggify-title) str)) + ((eq component 'keyword) + (replace-regexp-in-string + "_" "" + (funcall (or slug-function #'denote-sluggify-keyword) str))) + ((eq component 'signature) + (funcall (or slug-function #'denote-sluggify-signature) str))))) + (denote--trim-right-token-characters + (denote--replace-consecutive-token-characters + (denote--remove-dot-characters str-slug) component) component))) + +(defun denote-sluggify-title (str) + "Make STR an appropriate slug for title." + (downcase + (denote-slug-hyphenate + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/=]*" "" str)))) + +(defun denote-sluggify-signature (str) + "Make STR an appropriate slug for signature." + (downcase + (denote-slug-put-equals + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/-]*" "" str)))) + +(defun denote-sluggify-keyword (str) + "Sluggify STR while joining separate words." + (downcase + (replace-regexp-in-string "[][{}!@#$%^&*()+'\"?,.\|;:~`‘’“”/_ =-]*" "" str))) + +(defun denote-sluggify-keywords (keywords) + "Sluggify KEYWORDS, which is a list of strings." + (mapcar (lambda (keyword) + (denote-sluggify 'keyword keyword)) + keywords)) + +;;;;; Common helper functions + +(defun denote--file-empty-p (file) + "Return non-nil if FILE is empty." + (zerop (or (file-attribute-size (file-attributes file)) 0))) + +(defun denote-identifier-p (identifier) + "Return non-nil if IDENTIFIER string is a Denote identifier." + (string-match-p (format "\\`%s\\'" denote-id-regexp) identifier)) + +(defun denote-file-has-identifier-p (file) + "Return non-nil if FILE has a Denote identifier." + (denote-retrieve-filename-identifier file)) + +(defun denote-file-has-supported-extension-p (file) + "Return non-nil if FILE has supported extension. +Also account for the possibility of an added .gpg suffix. Supported +extensions are those implied by the variable `denote-file-type'." + (seq-some (lambda (e) + (string-suffix-p e file)) + (denote-file-type-extensions-with-encryption))) + +(defun denote-file-is-in-denote-directory-p (file) + "Return non-nil if FILE is in the variable `denote-directory'." + (string-prefix-p (denote-directory) (expand-file-name file))) + +(defun denote-filename-is-note-p (filename) + "Return non-nil if FILENAME is a valid name for a Denote note. +For our purposes, its path must be part of the variable +`denote-directory', it must have a Denote identifier in its name, and +use one of the extensions implied by the variable `denote-file-type'." + (and (denote-file-is-in-denote-directory-p filename) + (denote-file-has-identifier-p filename) + (denote-file-has-supported-extension-p filename))) + +(defun denote-file-is-note-p (file) + "Return non-nil if FILE is an actual Denote note. +For our purposes, a note must satisfy `file-regular-p' and +`denote-filename-is-note-p'." + (and (file-regular-p file) (denote-filename-is-note-p file))) + +(defun denote-file-has-denoted-filename-p (file) + "Return non-nil if FILE respects the file-naming scheme of Denote. + +This tests the rules of Denote's file-naming scheme. Sluggification is +ignored. It is done by removing all file name components and validating +what remains." + (let* ((initial-filename (file-name-nondirectory file)) + (filename initial-filename) + (title (denote-retrieve-filename-title file)) + (keywords-string (denote-retrieve-filename-keywords file)) + (signature (denote-retrieve-filename-signature file)) + (identifier (denote-retrieve-filename-identifier file))) + (when title + (setq filename (replace-regexp-in-string (concat "\\(--" (regexp-quote title) "\\).*\\'") "" filename nil nil 1))) + (when keywords-string + (setq filename (replace-regexp-in-string (concat "\\(__" (regexp-quote keywords-string) "\\).*\\'") "" filename nil nil 1))) + (when signature + (setq filename (replace-regexp-in-string (concat "\\(==" (regexp-quote signature) "\\).*\\'") "" filename nil nil 1))) + (when identifier + (if (string-match-p "@@" filename) + (setq filename (replace-regexp-in-string (concat "\\(@@" (regexp-quote identifier) "\\).*\\'") "" filename nil nil 1)) + (setq filename (replace-regexp-in-string (concat "\\(" (regexp-quote identifier) "\\).*\\'") "" filename nil nil 1)))) + ;; What remains should be the empty string or the file extension. + (and (not (string-prefix-p "." initial-filename)) + (or (string-empty-p filename) + (string-prefix-p "." filename))))) + +(defun denote-file-has-signature-p (file) + "Return non-nil if FILE has a Denote identifier." + (denote-retrieve-filename-signature file)) + +(defun denote-file-is-writable-and-supported-p (file) + "Return non-nil if FILE is writable and has supported extension." + ;; We do not want to test that the file is regular (exists) because we want + ;; this function to return t on files that are still unsaved. + (and (file-writable-p file) + (denote-file-has-supported-extension-p file))) + +(defun denote-get-file-name-relative-to-denote-directory (file) + "Return name of FILE relative to the variable `denote-directory'. +FILE must be an absolute path." + (when-let* ((dir (denote-directory)) + ((file-name-absolute-p file)) + (file-name (expand-file-name file)) + ((string-prefix-p dir file-name))) + (substring-no-properties file-name (length dir)))) + +(defun denote-extract-id-from-string (string) + "Return existing Denote identifier in STRING, else nil." + (when (string-match denote-id-regexp string) + (match-string-no-properties 0 string))) + +(defun denote--exclude-directory-regexp-p (file) + "Return non-nil if FILE matches `denote-excluded-directories-regexp'." + (and (stringp denote-excluded-directories-regexp) + (string-match-p denote-excluded-directories-regexp file))) + +(defun denote--directory-files-recursively-predicate (file) + "Predicate used by `directory-files-recursively' on FILE. + +Return t if FILE is valid, else return nil." + (let ((rel (denote-get-file-name-relative-to-denote-directory file))) + (cond + ((string-match-p "\\`\\." rel) nil) + ((string-match-p "/\\." rel) nil) + ((denote--exclude-directory-regexp-p rel) nil) + ((file-readable-p file))))) + +(defun denote--directory-all-files-recursively () + "Return list of all files in variable `denote-directory'. +Avoids traversing dotfiles (unconditionally) and whatever matches +`denote-excluded-directories-regexp'." + (directory-files-recursively + (denote-directory) + directory-files-no-dot-files-regexp + :include-directories + #'denote--directory-files-recursively-predicate + :follow-symlinks)) + +(defun denote--file-excluded-p (file) + "Return non-file if FILE matches `denote-excluded-files-regexp'." + (and denote-excluded-files-regexp + (string-match-p denote-excluded-files-regexp file))) + +(defun denote--directory-get-files () + "Return list with full path of valid files in variable `denote-directory'. +Consider files that satisfy `denote-file-has-identifier-p' and +are not backups." + (mapcar + #'expand-file-name + (seq-filter + (lambda (file) + (and (file-regular-p file) + (denote-file-has-identifier-p file) + (not (denote--file-excluded-p file)) + (not (backup-file-name-p file)))) + (denote--directory-all-files-recursively)))) + +(defun denote-directory-files (&optional files-matching-regexp omit-current text-only exclude-regexp) + "Return list of absolute file paths in variable `denote-directory'. +Files that match `denote-excluded-files-regexp' are excluded from the +list. + +Files only need to have an identifier. The return value may thus +include file types that are not implied by the variable +`denote-file-type'. + +With optional FILES-MATCHING-REGEXP, restrict files to those +matching the given regular expression. + +With optional OMIT-CURRENT as a non-nil value, do not include the +current Denote file in the returned list. + +With optional TEXT-ONLY as a non-nil value, limit the results to +text files that satisfy `denote-file-has-supported-extension-p'. + +With optional EXCLUDE-REGEXP exclude the files that match the given +regular expression. This is done after FILES-MATCHING-REGEXP and +OMIT-CURRENT have been applied." + (let ((files (denote--directory-get-files))) + (when (and omit-current buffer-file-name (denote-file-has-identifier-p buffer-file-name)) + (setq files (delete buffer-file-name files))) + (when files-matching-regexp + (setq files (seq-filter + (lambda (f) + (string-match-p files-matching-regexp (denote-get-file-name-relative-to-denote-directory f))) + files))) + (when text-only + (setq files (seq-filter #'denote-file-has-supported-extension-p files))) + (when exclude-regexp + (setq files (seq-remove + (lambda (file) + (string-match-p exclude-regexp file)) + files))) + files)) + +(defun denote-directory-subdirectories () + "Return list of subdirectories in variable `denote-directory'. +Omit dotfiles (such as .git) unconditionally. Also exclude +whatever matches `denote-excluded-directories-regexp'." + (seq-remove + (lambda (filename) + (let ((rel (denote-get-file-name-relative-to-denote-directory filename))) + (or (not (file-directory-p filename)) + (string-match-p "\\`\\." rel) + (string-match-p "/\\." rel) + (denote--exclude-directory-regexp-p rel)))) + (denote--directory-all-files-recursively))) + +;; TODO 2023-01-24: Perhaps there is a good reason to make this a user +;; option, but I am keeping it as a generic variable for now. +(defvar denote-encryption-file-extensions '(".gpg" ".age") + "List of strings specifying file extensions for encryption.") + +(defun denote-file-type-extensions-with-encryption () + "Derive `denote-file-type-extensions' plus `denote-encryption-file-extensions'." + (let ((file-extensions (denote-file-type-extensions)) + all) + (dolist (ext file-extensions) + (dolist (enc denote-encryption-file-extensions) + (push (concat ext enc) all))) + (append file-extensions all))) + +(defun denote-get-file-extension (file) + "Return extension of FILE with dot included. +Account for `denote-encryption-file-extensions'. In other words, +return something like .org.gpg if it is part of the file, else +return .org." + (let ((outer-extension (file-name-extension file :period))) + (if-let* (((member outer-extension denote-encryption-file-extensions)) + (file (file-name-sans-extension file)) + (inner-extension (file-name-extension file :period))) + (concat inner-extension outer-extension) + outer-extension))) + +(defun denote-get-file-extension-sans-encryption (file) + "Return extension of FILE with dot included and without the encryption part. +Build on top of `denote-get-file-extension' though always return +something like .org even if the actual file extension is +.org.gpg." + (let ((extension (denote-get-file-extension file))) + (if (string-match (regexp-opt denote-encryption-file-extensions) extension) + (substring extension 0 (match-beginning 0)) + extension))) + +(defun denote-get-path-by-id (id) + "Return absolute path of ID string in `denote-directory-files'." + (let ((files + (seq-filter + (lambda (file) + (string= id (denote-retrieve-filename-identifier file))) + (denote-directory-files)))) + (if (length< files 2) + (car files) + (seq-find + (lambda (file) + (let ((file-extension (denote-get-file-extension-sans-encryption file))) + (and (denote-file-has-supported-extension-p file) + (or (string= (denote--file-extension denote-file-type) + file-extension) + (string= ".org" file-extension) + (member file-extension (denote-file-type-extensions)))))) + files)))) + +(defun denote-get-relative-path-by-id (id &optional directory) + "Return relative path of ID string in `denote-directory-files'. +The path is relative to DIRECTORY (default: ‘default-directory’)." + (when-let* ((path (denote-get-path-by-id id))) + (file-relative-name path directory))) + +(defvar denote-file-history nil + "Minibuffer history of `denote-file-prompt'.") + +(defalias 'denote--file-history 'denote-file-history + "Compatibility alias for `denote-file-history'.") + +(defvar denote-file-prompt-latest-input nil + "Latest input passed to `denote-file-prompt'. +This is used for retrieving a value that is used to set a new default at +the title prompt of `denote-open-or-create' and related commands.") + +(defvar denote-file-prompt-use-files-matching-regexp nil + "The `denote-file-prompt' FILES-MATCHING-REGEXP value. +Only ever `let' bind this, otherwise the restriction will always be +there.") + +(defun denote-file-prompt (&optional files-matching-regexp prompt-text no-require-match) + "Prompt for file in variable `denote-directory'. +Files that match `denote-excluded-files-regexp' are excluded from the +list. + +With optional FILES-MATCHING-REGEXP, filter the candidates per +the given regular expression. + +With optional PROMPT-TEXT, use it instead of the default call to +select a file. + +With optional NO-REQUIRE-MATCH, accept the given input as-is. + +Return the absolute path to the matching file." + (let* ((default-directory (denote-directory)) + (relative-files (mapcar + #'denote-get-file-name-relative-to-denote-directory + (denote-directory-files + (or denote-file-prompt-use-files-matching-regexp files-matching-regexp) + :omit-current))) + (prompt (format "%s in %s: " + (or prompt-text "Select FILE") + (propertize (denote-directory) 'face 'denote-faces-prompt-current-name))) + (input (completing-read + prompt + (denote--completion-table 'file relative-files) + nil (unless no-require-match :require-match) + nil 'denote-file-history)) + (absolute-file (concat (denote-directory) input))) + ;; NOTE: This block is executed when no-require-match is t. It is useful + ;; for commands such as `denote-open-or-create` or similar. + (unless (file-exists-p absolute-file) + (setq denote-file-prompt-latest-input input) + (setq denote-file-history (delete input denote-file-history))) + ;; NOTE: We must always return an absolute path, even if it does not + ;; exist, because callers expect one. They handle a non-existent file + ;; appropriately. + absolute-file)) + +;;;; The sort mechanism + +(defgroup denote-sort nil + "Sort Denote files based on a file name component." + :group 'denote + :link '(info-link "(denote) Top") + :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) + +(defconst denote-sort-comparison-fallback-function #'string-collate-lessp + "String comparison function used by `denote-sort-files' subroutines.") + +(defconst denote-sort-components '(title keywords signature identifier) + "List of sorting keys applicable for `denote-sort-files' and related.") + +(defcustom denote-sort-identifier-comparison-function denote-sort-comparison-fallback-function + "Function to sort the DATE/IDENTIFIER component in file names. +The function accepts two arguments and must return a non-nil value if +the first argument is smaller than the second one." + :type 'function + :package-version '(denote . "4.0.0") + :group 'denote-sort) + +(defcustom denote-sort-title-comparison-function denote-sort-comparison-fallback-function + "Function to sort the TITLE component in file names. +The function accepts two arguments and must return a non-nil value if +the first argument is smaller than the second one." + :type 'function + :package-version '(denote . "3.1.0") + :group 'denote-sort) + +(defcustom denote-sort-keywords-comparison-function denote-sort-comparison-fallback-function + "Function to sort the KEYWORDS component in file names. +The function accepts two arguments and must return a non-nil value if +the first argument is smaller than the second one." + :type 'function + :package-version '(denote . "3.1.0") + :group 'denote-sort) + +(defcustom denote-sort-signature-comparison-function denote-sort-comparison-fallback-function + "Function to sort the SIGNATURE component in file names. +The function accepts two arguments and must return a non-nil value if +the first argument is smaller than the second one." + :type 'function + :package-version '(denote . "3.1.0") + :group 'denote-sort) + +(defcustom denote-sort-dired-extra-prompts '(sort-by-component reverse-sort) + "Determine what `denote-sort-dired' prompts for beside a search query. +This concerns the additional prompts issued by `denote-sort-dired' about +whether to sort by a given file name component and to then reverse the +sort. + +The value is a list of symbols, which can include the symbols +`sort-by-component', `reverse-sort', and `exclude-regexp'. The order is +significant, with the leftmost symbol coming first. + +These symbols correspond to the following: + +- A choice to select the file name component to sort by. +- A yes or no prompt on whether to reverse the sorting. +- A string (or regular expression) of files to be excluded from the results. + +If the value is nil, skip all prompts. In this scenario, the sorting is +done according to `denote-sort-dired-default-sort-component' and +`denote-sort-dired-default-reverse-sort'." + :type '(radio (const :tag "Do not prompt for anything" nil) + (set :tag "Available prompts" :greedy t + (const :tag "Sort by file name component" sort-by-component) + (const :tag "Reverse the sort" reverse-sort) + (const :tag "Exclude files matching regexp" exclude-regexp))) + :package-version '(denote . "4.0.0") + :group 'denote-sort) + +(defcustom denote-sort-dired-default-sort-component 'identifier + "Set the default file name component to sort by. +This is used only if `denote-sort-dired-extra-prompts' omits the +minibuffer prompt for which file name component to sort by." + :type '(radio + (const :tag "Sort by identifier (default)" identifier) + (const :tag "Sort by title" title) + (const :tag "Sort by keywords" keywords) + (const :tag "Sort by signature" signature)) + :package-version '(denote . "3.1.0") + :group 'denote-sort) + +(defcustom denote-sort-dired-default-reverse-sort nil + "If non-nil, reverse the sorting order by default. +This is used only if `denote-sort-dired-extra-prompts' omits the +minibuffer prompt that asks for a reverse sort or not." + :type 'boolean + :package-version '(denote . "3.1.0") + :group 'denote-sort) + +;; NOTE 2023-12-04: We can have compound sorting algorithms such as +;; title+signature, but I want to keep this simple for the time being. +;; Let us first hear from users to understand if there is a real need +;; for such a feature. +(defmacro denote-sort--define-lessp (component) + "Define function to sort by COMPONENT." + (let ((retrieve-fn (intern (format "denote-retrieve-filename-%s" component))) + (comparison-fn (intern (format "denote-sort-%s-comparison-function" component)))) + `(defun ,(intern (format "denote-sort-%s-lessp" component)) (file1 file2) + ,(format + "Return smallest among FILE1, FILE2 based on their %s. +The `%s' performs the comparison." + component comparison-fn) + (let* ((one (,retrieve-fn file1)) + (two (,retrieve-fn file2)) + (one-empty-p (or (null one) (string-empty-p one))) + (two-empty-p (or (null two) (string-empty-p two)))) + (cond + (one-empty-p nil) + ((and (not one-empty-p) two-empty-p) one) + (t (funcall (or ,comparison-fn denote-sort-comparison-fallback-function) one two))))))) + +;; TODO 2023-12-04: Subject to the above NOTE, we can also sort by +;; directory and by file length. +(denote-sort--define-lessp identifier) +(denote-sort--define-lessp title) +(denote-sort--define-lessp keywords) +(denote-sort--define-lessp signature) + +;;;###autoload +(defun denote-sort-files (files component &optional reverse) + "Returned sorted list of Denote FILES. + +With COMPONENT as a symbol among `denote-sort-components', +sort files based on the corresponding file name component. + +With COMPONENT as the symbol of a function, use it to perform the +sorting. In this case, the function is called with two arguments, as +described by `sort'. + +With COMPONENT as a nil value keep the original date-based +sorting which relies on the identifier of each file name. + +With optional REVERSE as a non-nil value, reverse the sort order." + (let* ((files-to-sort (copy-sequence files)) + (sort-fn (pcase component + ((pred functionp) component) + ('identifier #'denote-sort-identifier-lessp) + ('title #'denote-sort-title-lessp) + ('keywords #'denote-sort-keywords-lessp) + ('signature #'denote-sort-signature-lessp))) + (sorted-files (if sort-fn (sort files sort-fn) files-to-sort))) + (if reverse + (reverse sorted-files) + sorted-files))) + +(defun denote-sort-get-directory-files (files-matching-regexp sort-by-component &optional reverse omit-current exclude-regexp) + "Return sorted list of files in variable `denote-directory'. + +With FILES-MATCHING-REGEXP as a string limit files to those +matching the given regular expression. + +With SORT-BY-COMPONENT as a symbol among `denote-sort-components', +pass it to `denote-sort-files' to sort by the corresponding file +name component. + +With optional REVERSE as a non-nil value, reverse the sort order. + +With optional OMIT-CURRENT, do not include the current file in +the list. + +With optional EXCLUDE-REGEXP exclude the files that match the given +regular expression. This is done after FILES-MATCHING-REGEXP and +OMIT-CURRENT have been applied." + (denote-sort-files + (denote-directory-files files-matching-regexp omit-current nil exclude-regexp) + sort-by-component + reverse)) + +(defun denote-sort-get-links (files-matching-regexp sort-by-component current-file-type id-only &optional reverse exclude-regexp) + "Return sorted typographic list of links for FILES-MATCHING-REGEXP. + +With FILES-MATCHING-REGEXP as a string, match files stored in the +variable `denote-directory'. + +With SORT-BY-COMPONENT as a symbol among `denote-sort-components', +sort FILES-MATCHING-REGEXP by the given Denote file name +component. If SORT-BY-COMPONENT is nil or an unknown non-nil +value, default to the identifier-based sorting. + +With CURRENT-FILE-TYPE as a symbol among those specified in +the variable `denote-file-type' (or the `car' of each element in +`denote-file-types'), format the link accordingly. With a nil or +unknown non-nil value, default to the Org notation. + +With ID-ONLY as a non-nil value, produce links that consist only +of the identifier, thus deviating from CURRENT-FILE-TYPE. + +With optional REVERSE as a non-nil value, reverse the sort order. + +With optional EXCLUDE-REGEXP exclude the files that match the given +regular expression. This is done after FILES-MATCHING-REGEXP and +OMIT-CURRENT have been applied." + (denote-link--prepare-links + (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse exclude-regexp) + current-file-type + id-only)) + +(defvar denote-sort-component-history nil + "Minibuffer history of `denote-sort-component-prompt'.") + +(defalias 'denote-sort--component-hist 'denote-sort-component-history + "Compatibility alias for `denote-sort-component-history'.") + +(defun denote-sort-component-prompt () + "Prompt for sorting key among `denote-sort-components'." + (let ((default (car denote-sort-component-history))) + (intern + (completing-read + (format-prompt "Sort by file name component" default) + denote-sort-components nil :require-match + nil 'denote-sort-component-history default)))) + +(defvar denote-sort-exclude-files-history nil + "Minibuffer history for `denote-sort-exclude-files-prompt'.") + +(defun denote-sort-exclude-files-prompt () + "Prompt for regular expression of files to exclude." + ;; TODO 2024-12-03: Maybe use `read-regexp'? We do not use it + ;; elsewhere, so maybe this is fine. + (let ((default (car denote-sort-exclude-files-history))) + (read-string + (format-prompt "Exclude files matching REGEXP" default) + default 'denote-sort-exclude-files-history))) + +(defvar-local denote-sort--dired-buffer nil + "Buffer object of current `denote-sort-dired'.") + +(defun denote-sort-dired--prompts () + "Return list of prompts per `denote-sort-dired-extra-prompts'." + (let (sort-by-component reverse-sort exclude-rx) + (dolist (prompt denote-sort-dired-extra-prompts) + (pcase prompt + ('sort-by-component (setq sort-by-component (denote-sort-component-prompt))) + ('reverse-sort (setq reverse-sort (y-or-n-p "Reverse sort? "))) + ('exclude-regexp (setq exclude-rx (denote-sort-exclude-files-prompt))))) + (list sort-by-component reverse-sort exclude-rx))) + +;;;###autoload +(defun denote-sort-dired (files-matching-regexp sort-by-component reverse exclude-regexp) + "Produce Dired buffer with sorted files from variable `denote-directory'. +When called interactively, prompt for FILES-MATCHING-REGEXP and, +depending on the value of the user option `denote-sort-dired-extra-prompts', +also prompt for SORT-BY-COMPONENT, REVERSE, and EXCLUDE-REGEXP. + +1. FILES-MATCHING-REGEXP limits the list of Denote files to + those matching the provided regular expression. + +2. SORT-BY-COMPONENT sorts the files by their file name component (one + among `denote-sort-components'). If it is nil, sorting is performed + according to the user option `denote-sort-dired-default-sort-component', + falling back to the identifier. + +3. REVERSE is a boolean to reverse the order when it is a non-nil value. + If `denote-sort-dired-extra-prompts' is configured to skip this + prompt, then the sorting is done according to the user option + `denote-sort-dired-default-reverse-sort', falling back to + nil (i.e. no reverse sort). + +4. EXCLUDE-REGEXP excludes the files that match the given regular + expression. This is done after FILES-MATCHING-REGEXP and + OMIT-CURRENT have been applied. + +When called from Lisp, the arguments are a string, a symbol among +`denote-sort-components', a non-nil value, and a string, respectively." + (interactive + (append (list (denote-files-matching-regexp-prompt)) (denote-sort-dired--prompts))) + (let ((component (or sort-by-component + denote-sort-dired-default-sort-component + 'identifier)) + (reverse-sort (or reverse + denote-sort-dired-default-reverse-sort + nil)) + (exclude-rx (or exclude-regexp nil))) + (if-let* ((default-directory (denote-directory)) + (files (denote-sort-get-directory-files files-matching-regexp component reverse-sort nil exclude-rx)) + ;; NOTE 2023-12-04: Passing the FILES-MATCHING-REGEXP as + ;; buffer-name produces an error if the regexp contains a + ;; wildcard for a directory. I can reproduce this in emacs + ;; -Q and am not sure if it is a bug. Anyway, I will report + ;; it upstream, but even if it is fixed we cannot use it + ;; for now (whatever fix will be available for Emacs 30+). + (denote-sort-dired-buffer-name (format "Denote sort `%s' by `%s'" files-matching-regexp component)) + (buffer-name (format "Denote sort by `%s' at %s" component (format-time-string "%T")))) + (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name files))))) + (setq denote-sort--dired-buffer dired-buffer) + (with-current-buffer dired-buffer + (setq-local revert-buffer-function + (lambda (&rest _) + ;; FIXME 2025-01-04: Killing the buffer has + ;; the unintended side effect of affecting the + ;; window configuration when we call + ;; `denote-update-dired-buffers'. + (kill-buffer dired-buffer) + (denote-sort-dired files-matching-regexp component reverse-sort exclude-rx)))) + buffer-name) + (message "No matching files for: %s" files-matching-regexp)))) + +(defalias 'denote-dired 'denote-sort-dired + "Alias for `denote-sort-dired' command.") + +;;;; Keywords + +(defun denote-extract-keywords-from-path (path) + "Extract keywords from PATH and return them as a list of strings. +PATH must be a Denote-style file name where keywords are prefixed +with an underscore. + +If PATH has no such keywords, return nil. + +Also see `denote-retrieve-filename-keywords'." + (when-let* ((kws (denote-retrieve-filename-keywords path))) + (split-string kws "_" :omit-nulls))) + +(defalias 'denote-retrieve-filename-keywords-as-list 'denote-extract-keywords-from-path + "Alias for the function `denote-extract-keywords-from-path'") + +(define-obsolete-function-alias + 'denote--inferred-keywords + 'denote-infer-keywords-from-files + "4.0.0") + +(defun denote-infer-keywords-from-files (&optional files-matching-regexp) + "Return list of keywords in `denote-directory-files'. +With optional FILES-MATCHING-REGEXP, only extract keywords from the +matching files. Otherwise, do it for all files. + +Keep any duplicates. Users who do not want duplicates should refer to +the functions `denote-keywords'." + (when-let* ((files (denote-directory-files files-matching-regexp)) + (keywords (mapcan #'denote-extract-keywords-from-path files))) + (if-let* ((regexp denote-excluded-keywords-regexp)) + (seq-remove + (lambda (k) + (string-match-p regexp k)) + keywords) + keywords))) + +(defun denote-keywords (&optional files-matching-regexp) + "Return appropriate list of keyword candidates. +If `denote-infer-keywords' is non-nil, infer keywords from existing +notes and combine them into a list with `denote-known-keywords'. Else +use only the latter. + +In the case of keyword inferrence, use optional FILES-MATCHING-REGEXP, +to extract keywords only from the matching files. Otherwise, do it for +all files. + +Filter inferred keywords with the user option `denote-excluded-keywords-regexp'." + (delete-dups + (if denote-infer-keywords + (append (denote-infer-keywords-from-files files-matching-regexp) denote-known-keywords) + denote-known-keywords))) + +(defvar denote-keyword-history nil + "Minibuffer history of inputted keywords.") + +(defalias 'denote--keyword-history 'denote-keyword-history + "Compatibility alias for `denote-keyword-history'.") + +(make-obsolete + 'denote-convert-file-name-keywords-to-crm + nil + "3.0.0: Keywords are always returned as a list") + +(defun denote--keywords-crm (keywords &optional prompt initial) + "Use `completing-read-multiple' for KEYWORDS. +With optional PROMPT, use it instead of a generic text for file +keywords. With optional INITIAL, add it to the minibuffer as +initial input." + (delete-dups + (completing-read-multiple + (format-prompt (or prompt "New file KEYWORDS") nil) + keywords nil nil initial 'denote-keyword-history))) + +(defun denote-keywords-prompt (&optional prompt-text initial-keywords infer-from-files-matching-regexp) + "Prompt for one or more keywords. +Read entries as separate when they are demarcated by the +`crm-separator', which typically is a comma. + +With optional PROMPT-TEXT, use it to prompt the user for keywords. Else +use a generic prompt. With optional INITIAL-KEYWORDS use them as the +initial minibuffer text. + +With optional INFER-FROM-FILES-MATCHING-REGEXP, only infer keywords from +files that match the given regular expression, per the function +`denote-keywords'. + +Return an empty list if the minibuffer input is empty." + (denote--keywords-crm (denote-keywords infer-from-files-matching-regexp) prompt-text initial-keywords)) + +(defun denote-keywords-sort (keywords) + "Sort KEYWORDS if `denote-sort-keywords' is non-nil. +KEYWORDS is a list of strings, per `denote-keywords-prompt'." + (if denote-sort-keywords + (sort (copy-sequence keywords) #'string-collate-lessp) + keywords)) + +(defun denote-keywords-combine (keywords) + "Combine KEYWORDS list of strings into a single string. +Keywords are separated by the underscore character, per the +Denote file-naming scheme." + (string-join keywords "_")) + +(defun denote--keywords-add-to-history (keywords) + "Append KEYWORDS to `denote-keyword-history'." + (mapc + (lambda (kw) + (add-to-history 'denote-keyword-history kw)) + (delete-dups keywords))) + +;;;; File types + +(defvar denote-org-front-matter + "#+title: %s +#+date: %s +#+filetags: %s +#+identifier: %s +#+signature: %s +\n" + "Org front matter. +It is passed to `format' with arguments TITLE, DATE, KEYWORDS, +ID. Advanced users are advised to consult Info node `(denote) +Change the front matter format'.") + +(defvar denote-yaml-front-matter + "--- +title: %s +date: %s +tags: %s +identifier: %s +signature: %s +---\n\n" + "YAML (Markdown) front matter. +It is passed to `format' with arguments TITLE, DATE, KEYWORDS, +ID. Advanced users are advised to consult Info node `(denote) +Change the front matter format'.") + +(defvar denote-toml-front-matter + "+++ +title = %s +date = %s +tags = %s +identifier = %s +signature = %s ++++\n\n" + "TOML (Markdown) front matter. +It is passed to `format' with arguments TITLE, DATE, KEYWORDS, +ID. Advanced users are advised to consult Info node `(denote) +Change the front matter format'.") + +(defvar denote-text-front-matter + "title: %s +date: %s +tags: %s +identifier: %s +signature: %s +---------------------------\n\n" + "Plain text front matter. +It is passed to `format' with arguments TITLE, DATE, KEYWORDS, +ID. Advanced users are advised to consult Info node `(denote) +Change the front matter format'.") + +(defun denote-format-string-for-md-front-matter (s) + "Surround string S with quotes. + +This can be used in `denote-file-types' to format front mattter." + (format "%S" s)) + +(defun denote-trim-whitespace (s) + "Trim whitespace around string S. +This can be used in `denote-file-types' to format front mattter." + (string-trim s)) + +(defun denote--trim-quotes (s) + "Trim quotes around string S." + (let ((trims "[\"']+")) + (string-trim s trims trims))) + +(defun denote-trim-whitespace-then-quotes (s) + "Trim whitespace then quotes around string S. +This can be used in `denote-file-types' to format front mattter." + (denote--trim-quotes (denote-trim-whitespace s))) + +(defun denote-format-string-for-org-front-matter (s) + "Return string S as-is for Org or plain text front matter." + s) + +(defun denote-format-keywords-for-md-front-matter (keywords) + "Format front matter KEYWORDS for markdown file type. +KEYWORDS is a list of strings. Consult the `denote-file-types' +for how this is used." + (format "[%s]" (mapconcat (lambda (k) (format "%S" k)) keywords ", "))) + +(defun denote-format-keywords-for-text-front-matter (keywords) + "Format front matter KEYWORDS for text file type. +KEYWORDS is a list of strings. Consult the `denote-file-types' +for how this is used." + (string-join keywords " ")) + +(defun denote-format-keywords-for-org-front-matter (keywords) + "Format front matter KEYWORDS for org file type. +KEYWORDS is a list of strings. Consult the `denote-file-types' +for how this is used." + (if keywords + (format ":%s:" (string-join keywords ":")) + "")) + +(defun denote-extract-keywords-from-front-matter (keywords-string) + "Extract keywords list from front matter KEYWORDS-STRING. +Split KEYWORDS-STRING into a list of strings. + +Consult the `denote-file-types' for how this is used." + (split-string keywords-string "[:,\s]+" t "[][ \"']+")) + +(defun denote-extract-date-from-front-matter (date-string) + "Extract date object from front matter DATE-STRING. + +Consult the `denote-file-types' for how this is used." + (let ((date-string (denote-trim-whitespace date-string))) + (if (string-empty-p date-string) + nil + (date-to-time date-string)))) + +(defvar denote-file-types + '((org + :extension ".org" + :front-matter denote-org-front-matter + :title-key-regexp "^#\\+title\\s-*:" + :title-value-function denote-format-string-for-org-front-matter + :title-value-reverse-function denote-trim-whitespace + :keywords-key-regexp "^#\\+filetags\\s-*:" + :keywords-value-function denote-format-keywords-for-org-front-matter + :keywords-value-reverse-function denote-extract-keywords-from-front-matter + :signature-key-regexp "^#\\+signature\\s-*:" + :signature-value-function denote-format-string-for-org-front-matter + :signature-value-reverse-function denote-trim-whitespace + :identifier-key-regexp "^#\\+identifier\\s-*:" + :identifier-value-function denote-format-string-for-org-front-matter + :identifier-value-reverse-function denote-trim-whitespace + :date-key-regexp "^#\\+date\\s-*:" + :date-value-function denote-date-org-timestamp + :date-value-reverse-function denote-extract-date-from-front-matter + :link denote-org-link-format + :link-in-context-regexp denote-org-link-in-context-regexp) + (markdown-yaml + :extension ".md" + :front-matter denote-yaml-front-matter + :title-key-regexp "^title\\s-*:" + :title-value-function denote-format-string-for-md-front-matter + :title-value-reverse-function denote-trim-whitespace-then-quotes + :keywords-key-regexp "^tags\\s-*:" + :keywords-value-function denote-format-keywords-for-md-front-matter + :keywords-value-reverse-function denote-extract-keywords-from-front-matter + :signature-key-regexp "^signature\\s-*:" + :signature-value-function denote-format-string-for-md-front-matter + :signature-value-reverse-function denote-trim-whitespace-then-quotes + :identifier-key-regexp "^identifier\\s-*:" + :identifier-value-function denote-format-string-for-md-front-matter + :identifier-value-reverse-function denote-trim-whitespace-then-quotes + :date-key-regexp "^date\\s-*:" + :date-value-function denote-date-rfc3339 + :date-value-reverse-function denote-extract-date-from-front-matter + :link denote-md-link-format + :link-in-context-regexp denote-md-link-in-context-regexp) + (markdown-toml + :extension ".md" + :front-matter denote-toml-front-matter + :title-key-regexp "^title\\s-*=" + :title-value-function denote-format-string-for-md-front-matter + :title-value-reverse-function denote-trim-whitespace-then-quotes + :keywords-key-regexp "^tags\\s-*=" + :keywords-value-function denote-format-keywords-for-md-front-matter + :keywords-value-reverse-function denote-extract-keywords-from-front-matter + :signature-key-regexp "^signature\\s-*=" + :signature-value-function denote-format-string-for-md-front-matter + :signature-value-reverse-function denote-trim-whitespace-then-quotes + :identifier-key-regexp "^identifier\\s-*=" + :identifier-value-function denote-format-string-for-md-front-matter + :identifier-value-reverse-function denote-trim-whitespace-then-quotes + :date-key-regexp "^date\\s-*=" + :date-value-function denote-date-rfc3339 + :date-value-reverse-function denote-extract-date-from-front-matter + :link denote-md-link-format + :link-in-context-regexp denote-md-link-in-context-regexp) + (text + :extension ".txt" + :front-matter denote-text-front-matter + :title-key-regexp "^title\\s-*:" + :title-value-function denote-format-string-for-org-front-matter + :title-value-reverse-function denote-trim-whitespace + :keywords-key-regexp "^tags\\s-*:" + :keywords-value-function denote-format-keywords-for-text-front-matter + :keywords-value-reverse-function denote-extract-keywords-from-front-matter + :signature-key-regexp "^signature\\s-*:" + :signature-value-function denote-format-string-for-org-front-matter + :signature-value-reverse-function denote-trim-whitespace + :identifier-key-regexp "^identifier\\s-*:" + :identifier-value-function denote-format-string-for-org-front-matter + :identifier-value-reverse-function denote-trim-whitespace + :date-key-regexp "^date\\s-*:" + :date-value-function denote-date-iso-8601 + :date-value-reverse-function denote-extract-date-from-front-matter + :link denote-org-link-format + :link-in-context-regexp denote-org-link-in-context-regexp)) + "Alist of variable `denote-file-type' and their format properties. + +Each element is of the form (SYMBOL PROPERTY-LIST). SYMBOL is one of +those specified in the user option `denote-file-type' or an arbitrary +symbol that defines a new file type. + +PROPERTY-LIST is a plist that consists of the following elements: + +- `:extension' is a string with the file extension including the + period. + +- `:date-function' is a function that can format a date. See the + functions `denote-date-iso-8601', `denote-date-rfc3339', and + `denote-date-org-timestamp'. + +- `:front-matter' is either a string passed to `format' or a + variable holding such a string. The `format' function accepts + four arguments, which come from `denote' in this order: TITLE, + DATE, KEYWORDS, IDENTIFIER. Read the doc string of `format' on + how to reorder arguments. + +- `:title-key-regexp' is a regular expression that is used to + retrieve the title line in a file. The first line matching + this regexp is considered the title line. + +- `:title-value-function' is the function used to format the raw + title string for inclusion in the front matter (e.g. to + surround it with quotes). Use the `identity' function if no + further processing is required. + +- `:title-value-reverse-function' is the function used to + retrieve the raw title string from the front matter. It + performs the reverse of `:title-value-function'. + +- `:keywords-key-regexp' is a regular expression used to retrieve + the keywords' line in the file. The first line matching this + regexp is considered the keywords' line. + +- `:keywords-value-function' is the function used to format the + keywords' list of strings as a single string, with appropriate + delimiters, for inclusion in the front matter. + +- `:keywords-value-reverse-function' is the function used to + retrieve the keywords' value from the front matter. It + performs the reverse of the `:keywords-value-function'. + +- `:link' is a string, or variable holding a string, that + specifies the format of a link. See the variables + `denote-org-link-format', `denote-md-link-format'. + +- `:link-in-context-regexp' is a regular expression that is used + to match the aforementioned link format. See the variables + `denote-org-link-in-context-regexp',`denote-md-link-in-context-regexp'. + +If the user option `denote-file-type' is nil, use the first element of +this list for new note creation. The default is `org'.") + +(defun denote--file-extension (file-type) + "Return file type extension based on FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :extension)) + +(defun denote--front-matter (file-type) + "Return front matter based on FILE-TYPE." + (let ((prop (plist-get + (alist-get file-type denote-file-types) + :front-matter))) + (if (symbolp prop) + (symbol-value prop) + prop))) + +(defun denote--title-key-regexp (file-type) + "Return the title key regexp associated to FILE-TYPE." + (or (plist-get + (alist-get file-type denote-file-types) + :title-key-regexp) + "^DenoteUserWantsEmptyFieldSoHandleIt")) ; Will not be found + +(defun denote--title-value-function (file-type) + "Convert title string to a front matter title, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :title-value-function)) + +(defun denote--title-value-reverse-function (file-type) + "Convert front matter title to the title string, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :title-value-reverse-function)) + +(defun denote--keywords-key-regexp (file-type) + "Return the keywords key regexp associated to FILE-TYPE." + (or (plist-get + (alist-get file-type denote-file-types) + :keywords-key-regexp) + "^DenoteUserWantsEmptyFieldSoHandleIt")) ; Will not be found + +(defun denote--keywords-value-function (file-type) + "Convert keywords' list to front matter keywords, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :keywords-value-function)) + +(defun denote--keywords-value-reverse-function (file-type) + "Convert front matter keywords to keywords' list, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :keywords-value-reverse-function)) + +(defun denote--signature-key-regexp (file-type) + "Return the signature key regexp associated to FILE-TYPE." + (or (plist-get + (alist-get file-type denote-file-types) + :signature-key-regexp) + "^DenoteUserWantsEmptyFieldSoHandleIt")) ; Will not be found + +(defun denote--signature-value-function (file-type) + "Convert signature string to front matter signature, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :signature-value-function)) + +(defun denote--signature-value-reverse-function (file-type) + "Convert front matter signature to signature string, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :signature-value-reverse-function)) + +(defun denote--identifier-key-regexp (file-type) + "Return the identifier key regexp associated to FILE-TYPE." + (or (plist-get + (alist-get file-type denote-file-types) + :identifier-key-regexp) + "^DenoteUserWantsEmptyFieldSoHandleIt")) ; Will not be found + +(defun denote--identifier-value-function (file-type) + "Convert identifier string to front matter identifier, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :identifier-value-function)) + +(defun denote--identifier-value-reverse-function (file-type) + "Convert front matter identifier to identifier string, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :identifier-value-reverse-function)) + +(defun denote--date-key-regexp (file-type) + "Return the date key regexp associated to FILE-TYPE." + (or (plist-get + (alist-get file-type denote-file-types) + :date-key-regexp) + "^DenoteUserWantsEmptyFieldSoHandleIt")) ; Will not be found + +(defun denote--date-value-function (file-type) + "Convert date object to front matter date, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :date-value-function)) + +(defun denote--date-value-reverse-function (file-type) + "Convert front matter date to date object, per FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :date-value-reverse-function)) + +(defun denote--link-format (file-type) + "Return link format extension based on FILE-TYPE." + (let ((prop (plist-get + (alist-get file-type denote-file-types) + :link))) + (if (symbolp prop) + (symbol-value prop) + prop))) + +(defun denote--link-in-context-regexp (file-type) + "Return link regexp in context based on FILE-TYPE." + (let ((prop (plist-get + (alist-get file-type denote-file-types) + :link-in-context-regexp))) + (if (symbolp prop) + (symbol-value prop) + prop))) + +(defun denote-file-type-extensions () + "Return all file type extensions in `denote-file-types'." + (delete-dups + (mapcar (lambda (type) + (plist-get (cdr type) :extension)) + denote-file-types))) + +(defun denote--file-type-keys () + "Return all `denote-file-types' keys." + (delete-dups (mapcar #'car denote-file-types))) + +(defun denote--get-component-key-regexp-function (component) + "Return COMPONENT's key regexp function. + +COMPONENT can be one of `title', `keywords', `identifier', `date', `signature'." + (pcase component + ('title #'denote--title-key-regexp) + ('keywords #'denote--keywords-key-regexp) + ('signature #'denote--signature-key-regexp) + ('date #'denote--date-key-regexp) + ('identifier #'denote--identifier-key-regexp))) + +(defun denote--format-front-matter (title date keywords id signature filetype) + "Front matter for new notes. + +TITLE, SIGNATURE, and ID are strings. DATE is a date object. KEYWORDS +is a list of strings. FILETYPE is one of the values of variable +`denote-file-type'." + (let* ((fm (denote--front-matter filetype)) + (title-value-function (denote--title-value-function filetype)) + (keywords-value-function (denote--keywords-value-function filetype)) + (id-value-function (denote--identifier-value-function filetype)) + (signature-value-function (denote--signature-value-function filetype)) + (title-string (if title-value-function (funcall title-value-function title) "")) + (date-string (denote--format-front-matter-date date filetype)) + (keywords-string (if keywords-value-function (funcall keywords-value-function (denote-sluggify-keywords keywords)) "")) + (id-string (if id-value-function (funcall id-value-function id) "")) + (signature-string (if signature-value-function (funcall signature-value-function (denote-sluggify-signature signature)) "")) + (new-front-matter (if fm (format fm title-string date-string keywords-string id-string signature-string) ""))) + ;; Remove lines with empty values if the corresponding component + ;; is not in `denote-front-matter-components-present-even-if-empty-value'. + (with-temp-buffer + (insert new-front-matter) + (dolist (component '(title date keywords signature identifier)) + (let ((value (pcase component ('title title) ('keywords keywords) ('signature signature) ('date date) ('identifier id))) + (component-key-regexp-function (denote--get-component-key-regexp-function component))) + (goto-char (point-min)) + (when (and (not (denote--component-has-value-p component value)) + (not (memq component denote-front-matter-components-present-even-if-empty-value)) + (re-search-forward (funcall component-key-regexp-function filetype) nil t 1)) + (goto-char (line-beginning-position)) + (delete-region (line-beginning-position) (line-beginning-position 2))))) + (buffer-string)))) + +;;;; Front matter or content retrieval functions + +(defun denote-retrieve-filename-identifier (file) + "Extract identifier from FILE name, if present, else return nil. + +To create a new one from a date, refer to the function +`denote-get-identifier'." + (let ((filename (file-name-nondirectory file))) + (cond ((string-match (concat "\\`" denote-id-regexp) filename) + (match-string-no-properties 0 filename)) + ((string-match (concat "@@\\(?1:" denote-id-regexp "\\)") filename) + (match-string-no-properties 1 filename))))) + +;; TODO 2023-12-08: Maybe we can only use +;; `denote-retrieve-filename-identifier' and remove this function. +(defun denote-retrieve-filename-identifier-with-error (file) + "Extract identifier from FILE name, if present, else signal an error." + (or (denote-retrieve-filename-identifier file) + (error "Cannot find `%s' as a file with a Denote identifier" file))) + +(defun denote-get-identifier (date) + "Convert DATE into a Denote identifier using `denote-id-format'. +If DATE is nil, return an empty string as the identifier." + (if date + (format-time-string denote-id-format date) + "")) + +(defvar denote--used-ids nil + "Hash table of used identifiers. +This variable should be set only for the duration of a command. +It should stay nil otherwise.") + +(define-obsolete-function-alias + 'denote-create-unique-file-identifier + 'denote-get-identifier + "4.0.0") + +(defun denote-retrieve-filename-keywords (file) + "Extract keywords from FILE name, if present, else return nil. +Return matched keywords as a single string. + +Also see `denote-extract-keywords-from-path' (alias +`denote-retrieve-filename-keywords-as-list')." + (let ((filename (file-name-nondirectory file))) + (when (string-match denote-keywords-regexp filename) + (match-string 1 filename)))) + +(defun denote-retrieve-filename-signature (file) + "Extract signature from FILE name, if present, else return nil." + (let ((filename (file-name-nondirectory file))) + (when (string-match denote-signature-regexp filename) + (match-string 1 filename)))) + +(defun denote-retrieve-filename-title (file) + "Extract Denote title component from FILE name, else return nil." + (let ((filename (file-name-nondirectory file))) + (when (string-match denote-title-regexp filename) + (match-string 1 filename)))) + +(defun denote--file-with-temp-buffer-subr (file) + "Return path to FILE or its buffer together with the appropriate function. +Subroutine of `denote--file-with-temp-buffer'." + (let* ((buffer (get-file-buffer file)) + (file-exists (file-exists-p file)) + (buffer-modified (buffer-modified-p buffer))) + (cond + ((or (and file-exists + buffer + (not buffer-modified) + (not (eq buffer-modified 'autosaved))) + (and file-exists (not buffer))) + (cons #'insert-file-contents file)) + (buffer + (cons #'insert-buffer buffer)) + ;; (t + ;; (error "Cannot find anything about file `%s'" file)) + ))) + +(defmacro denote--file-with-temp-buffer (file &rest body) + "If FILE exists, insert its contents in a temp buffer and call BODY." + (declare (indent 1)) + `(when-let* ((file-and-function (denote--file-with-temp-buffer-subr ,file))) + (with-temp-buffer + (funcall (car file-and-function) (cdr file-and-function)) + (goto-char (point-min)) + ,@body))) + +(defmacro denote--define-retrieve-front-matter (component scope) + "Define a function to retrieve front matter for COMPONENT given SCOPE. +The COMPONENT is one of the file name components that has a +corresponding front matter entry. SCOPE is a symbol of either `value' +or `line', referring to what the function should retrieve." + (declare (indent 1)) + `(defun ,(intern (format "denote-retrieve-front-matter-%s-%s" component scope)) (file file-type) + (when file-type + (denote--file-with-temp-buffer file + (when (re-search-forward (,(intern (format "denote--%s-key-regexp" component)) file-type) nil t 1) + ,(cond + ((eq scope 'value) + `(funcall (,(intern (format "denote--%s-value-reverse-function" component)) file-type) + (buffer-substring-no-properties (point) (line-end-position)))) + ((eq scope 'line) + '(buffer-substring-no-properties (line-beginning-position) (line-end-position))) + (t (error "`%s' is not a known scope" scope)))))))) + +(denote--define-retrieve-front-matter title value) +(denote--define-retrieve-front-matter title line) +(denote--define-retrieve-front-matter keywords value) +(denote--define-retrieve-front-matter keywords line) +(denote--define-retrieve-front-matter signature value) +(denote--define-retrieve-front-matter signature line) +(denote--define-retrieve-front-matter identifier value) +(denote--define-retrieve-front-matter identifier line) +(denote--define-retrieve-front-matter date value) +(denote--define-retrieve-front-matter date line) + +;; These are private front matter retrieval functions, working with a content parameter + +(defmacro denote--define-retrieve-front-matter-from-content (component scope) + "Define a function to retrieve front matter for COMPONENT given SCOPE. +The COMPONENT is one of the file name components that has a +corresponding front matter entry. SCOPE is a symbol of either `value' +or `line', referring to what the function should retrieve." + (declare (indent 1)) + `(defun ,(intern (format "denote--retrieve-front-matter-%s-%s-from-content" component scope)) (content file-type) + (when file-type + (with-temp-buffer + (insert content) + (goto-char (point-min)) + (when (re-search-forward (,(intern (format "denote--%s-key-regexp" component)) file-type) nil t 1) + ,(cond + ((eq scope 'value) + `(funcall (,(intern (format "denote--%s-value-reverse-function" component)) file-type) + (buffer-substring-no-properties (point) (line-end-position)))) + ((eq scope 'line) + '(buffer-substring-no-properties (line-beginning-position) (line-end-position))) + (t (error "`%s' is not a known scope" scope)))))))) + +(denote--define-retrieve-front-matter-from-content title value) +(denote--define-retrieve-front-matter-from-content title line) +(denote--define-retrieve-front-matter-from-content keywords value) +(denote--define-retrieve-front-matter-from-content keywords line) +(denote--define-retrieve-front-matter-from-content signature value) +(denote--define-retrieve-front-matter-from-content signature line) +(denote--define-retrieve-front-matter-from-content identifier value) +(denote--define-retrieve-front-matter-from-content identifier line) +(denote--define-retrieve-front-matter-from-content date value) +(denote--define-retrieve-front-matter-from-content date line) + +(defalias 'denote-retrieve-title-value 'denote-retrieve-front-matter-title-value + "Alias for `denote-retrieve-front-matter-title-value'.") + +(defalias 'denote-retrieve-title-line 'denote-retrieve-front-matter-title-line + "Alias for `denote-retrieve-front-matter-title-line'.") + +(defalias 'denote-retrieve-keywords-value 'denote-retrieve-front-matter-keywords-value + "Alias for `denote-retrieve-front-matter-keywords-value'.") + +(defalias 'denote-retrieve-keywords-line 'denote-retrieve-front-matter-keywords-line + "Alias for `denote-retrieve-front-matter-keywords-line'.") + +(defun denote-retrieve-title-or-filename (file type) + "Return appropriate title for FILE given its TYPE. +This is a wrapper for `denote-retrieve-front-matter-title-value' and +`denote-retrieve-filename-title'." + (let ((has-denoted-filename (denote-file-has-denoted-filename-p file)) + (has-supported-extension (denote-file-has-supported-extension-p file))) + (cond ((and has-denoted-filename has-supported-extension) + (or (denote-retrieve-front-matter-title-value file type) + (denote-retrieve-filename-title file) + "")) + (has-denoted-filename + (or (denote-retrieve-filename-title file) "")) + (t + (file-name-base file))))) + +(make-obsolete 'denote--retrieve-location-in-xrefs 'denote-retrieve-groups-xref-query "4.0.0") + +(define-obsolete-function-alias + 'denote--retrieve-group-in-xrefs + 'denote-retrieve-groups-xref-query + "4.0.0") + +(defun denote-retrieve-groups-xref-query (query &optional files-matching-regexp) + "Access location of xrefs for QUERY and group them per file. +Limit the search to text files. With optional FILES-MATCHING-REGEXP, +pass it to `denote-directory-files'." + (when-let* ((files (denote-directory-files files-matching-regexp nil :text-only)) + (locations (mapcar #'xref-match-item-location (xref-matches-in-files query files)))) + (mapcar #'xref-location-group locations))) + +(define-obsolete-function-alias + 'denote--retrieve-files-in-xrefs + 'denote-retrieve-files-xref-query + "4.0.0") + +(defun denote-retrieve-files-xref-query (query &optional files-matching-regexp) + "Return sorted, deduplicated file names with matches for QUERY in their contents. +Limit the search to text files. With optional FILES-MATCHING-REGEXP, +pass it to `denote-directory-files'." + (sort + (delete-dups + (denote-retrieve-groups-xref-query query files-matching-regexp)) + #'string-collate-lessp)) + +(defvar denote-query--last-files nil + "List of files matched by the last call to `denote-make-links-buffer'.") + +(defvar denote-query--last-query nil + "String of the last call to `denote-make-links-buffer'.") + +(defvar denote-query--omit-current t + "When non-nil `denote-make-links-buffer' omits the current file.") + +(defun denote-retrieve-xref-alist (query &optional files) + "Return xref alist of absolute file paths with location of matches for QUERY. +Optional FILES can be a list of files to search for. It can also be a +regular expression, which means to use the text files in the variable +`denote-directory' that match that regexp. + +If FILES is not given, use all text files as returned by +`denote-directory-files'." + (let ((xref-file-name-display 'abs)) + (xref--analyze + (xref-matches-in-files + query + (if (and files (listp files)) + files + (denote-directory-files files denote-query--omit-current :text-only)))))) + +;;;; New note + +;;;;; Common helpers for new notes + +(defun denote-format-file-name (dir-path id keywords title extension signature) + "Format file name. +DIR-PATH, ID, KEYWORDS, TITLE, EXTENSION and SIGNATURE are +expected to be supplied by `denote' or equivalent command. + +DIR-PATH is a string pointing to a directory. It ends with a +forward slash (the function `denote-directory' makes sure this is +the case when returning the value of the variable `denote-directory'). +DIR-PATH cannot be nil or an empty string. + +ID is a string holding the identifier of the note. It can be an +empty string, in which case its respective file name component is +not added to the base file name. + +DIR-PATH and ID form the base file name. + +KEYWORDS is a list of strings that is reduced to a single string +by `denote-keywords-combine'. KEYWORDS can be an empty list or +a nil value, in which case the relevant file name component is +not added to the base file name. + +TITLE and SIGNATURE are strings. They can be an empty string, in +which case their respective file name component is not added to +the base file name. + +EXTENSION is a string that contains a dot followed by the file +type extension. It can be an empty string or a nil value, in +which case it is not added to the base file name." + (cond + ((null dir-path) + (error "DIR-PATH must not be nil")) + ((string-empty-p dir-path) + (error "DIR-PATH must not be an empty string")) + ((not (string-suffix-p "/" dir-path)) + (error "DIR-PATH does not end with a / as directories ought to"))) + (let ((file-name "") + (components (seq-union denote-file-name-components-order + '(identifier signature title keywords)))) + (dolist (component components) + (cond ((and (eq component 'identifier) id (not (string-empty-p id))) + (setq file-name (concat file-name "@@" id))) + ((and (eq component 'title) title (not (string-empty-p title))) + (setq file-name (concat file-name "--" (denote-sluggify 'title title)))) + ((and (eq component 'keywords) keywords) + (setq file-name (concat file-name "__" (denote-keywords-combine (denote-sluggify-keywords keywords))))) + ((and (eq component 'signature) signature (not (string-empty-p signature))) + (setq file-name (concat file-name "==" (denote-sluggify 'signature signature)))))) + (when (string-empty-p file-name) + (error "There should be at least one file name component")) + (setq file-name (concat file-name extension)) + ;; Do not prepend identifier with @@ if it is the first component and has the format 00000000T000000. + (when (and (string-prefix-p "@@" file-name) + (string-match-p (concat "\\`" denote-id-regexp "\\'") id)) ; This is always true for now. + (setq file-name (substring file-name 2))) + (concat dir-path file-name))) + +;; Adapted from `org-hugo--org-date-time-to-rfc3339' in the `ox-hugo' +;; package: . +(defun denote-date-rfc3339 (date) + "Format DATE using the RFC3339 specification." + (if date + (replace-regexp-in-string + "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2" + (format-time-string "%FT%T%z" date)) + "")) + +(defun denote-date-org-timestamp (date) + "Format DATE using the Org inactive timestamp notation." + (if date + (format-time-string "[%F %a %R]" date) + "")) + +(defun denote-date-iso-8601 (date) + "Format DATE according to ISO 8601 standard." + (if date + (format-time-string "%F" date) + "")) + +(defun denote--format-front-matter-date (date file-type) + "Expand DATE in an appropriate format for FILE-TYPE." + (let ((format denote-date-format)) + (cond + (format + (if date (format-time-string format date) "")) + ((when-let* ((fn (denote--date-value-function file-type))) + (funcall fn date))) + (t + (denote-date-org-timestamp date))))) + +(defun denote--prepare-note (title keywords date id directory file-type template signature) + "Prepare a new note file and return its path. + +Arguments TITLE, KEYWORDS, DATE, ID, DIRECTORY, FILE-TYPE, +TEMPLATE, and SIGNATURE should be valid for note creation." + (let* ((path (denote-format-file-name + directory id keywords title (denote--file-extension file-type) signature)) + (buffer (find-file path)) + (header (denote--format-front-matter title date keywords id signature file-type))) + (when (file-regular-p path) + (user-error "A file named `%s' already exists" path)) + (with-current-buffer buffer + (insert header) + (insert (cond + ((stringp template) template) + ((functionp template) (funcall template)) + (t (user-error "Invalid template"))))) + path)) + +(defun denote--dir-in-denote-directory-p (directory) + "Return non-nil if DIRECTORY is in variable `denote-directory'." + (string-prefix-p (denote-directory) (expand-file-name directory))) + +(defun denote--valid-file-type (filetype) + "Return a valid filetype symbol given the argument FILETYPE. +If none is found, the first element of `denote-file-types' is +returned." + (let ((type (cond + ((stringp filetype) (intern filetype)) + ((symbolp filetype) filetype) + (t (error "The `%s' is neither a string nor a symbol" filetype))))) + (cond ((memq type (denote--file-type-keys)) + type) + ((null denote-file-types) + (user-error "At least one file type must be defined in `denote-file-types' to create a note")) + (t + (caar denote-file-types))))) + +(defun denote--date-add-current-time (date) + "Add current time to DATE, if necessary. +The idea is to turn 2020-01-15 into 2020-01-15 16:19 so that the +hour and minute component is not left to 00:00. + +This reduces the burden on the user who would otherwise need to +input that value in order to avoid the error of duplicate +identifiers. + +It also addresses a difference between Emacs 28 and Emacs 29 +where the former does not read dates without a time component." + (if (<= (length date) 10) + (format "%s %s" date (format-time-string "%H:%M:%S" (current-time))) + date)) + +(define-obsolete-function-alias + 'denote-parse-date + 'denote-valid-date-p + "4.0.0") + +(defun denote-valid-date-p (date) + "Return DATE as a valid date. +A valid DATE is a value that can be parsed by either +`decode-time' or `date-to-time'. Those functions signal an error +if DATE is a value they do not recognise. + +If DATE is nil or an empty string, return nil." + (cond ((null date) + nil) + ((and (stringp date) (string-empty-p date)) + nil) + ((and (or (numberp date) (listp date)) + (decode-time date)) + date) + (t ; non-empty strings (e.g. "2024-01-01", "2024-01-01 12:00", etc.) + (date-to-time (denote--date-add-current-time date))))) + +(defun denote--id-to-date (identifier) + "Convert IDENTIFIER string to YYYY-MM-DD." + (if (denote-identifier-p identifier) + (replace-regexp-in-string + "\\([0-9]\\{4\\}\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\).*" + "\\1-\\2-\\3" + identifier) + (error "`%s' does not look like a Denote identifier per `denote-id-regexp'" identifier))) + +(defun denote--buffer-file-names () + "Return file names of Denote buffers." + (delq nil + (mapcar + (lambda (buffer) + (when-let* (((buffer-live-p buffer)) + (file (buffer-file-name buffer)) + ((denote-filename-is-note-p file))) + file)) + (buffer-list)))) + +(defun denote--get-all-used-ids () + "Return a hash-table of all used identifiers. +It checks files in variable `denote-directory' and active buffer files." + (let* ((ids (make-hash-table :test 'equal)) + (file-names (mapcar + (lambda (file) (file-name-nondirectory file)) + (denote-directory-files))) + (names (append file-names (denote--buffer-file-names)))) + (dolist (name names) + (when-let* ((id (denote-retrieve-filename-identifier name))) + (puthash id t ids))) + ids)) + +(defun denote--find-first-unused-id (id) + "Return the first unused id starting at ID. +If ID is already used, increment it 1 second at a time until an +available id is found." + (let ((used-ids (or denote--used-ids (denote--get-all-used-ids))) + (current-id id) + (iteration 0)) + (while (gethash current-id used-ids) + ;; Prevent infinite loop if `denote-id-format' is misconfigured + (setq iteration (1+ iteration)) + (when (>= iteration 10000) + (user-error "A unique identifier could not be found")) + (setq current-id (denote-get-identifier (time-add (date-to-time current-id) 1)))) + current-id)) + +(defvar denote-command-prompt-history nil + "Minibuffer history for `denote-command-prompt'.") + +(defalias 'denote--command-prompt-history 'denote-command-prompt-history + "Compatibility alias for `denote-command-prompt-history'.") + +(defun denote-command-prompt () + "Prompt for command among `denote-commands-for-new-notes'." + (let ((default (car denote-command-prompt-history))) + (intern + (completing-read + (format-prompt "Run note-creating Denote command" default) + denote-commands-for-new-notes nil :require-match + nil 'denote-command-prompt-history default)))) + +;;;;; The `denote' command and its prompts + +(defun denote--prompt-with-completion-p (fn) + "Return non-nil if FN prompt should perform completion. +FN is one among `denote-prompts-with-history-as-completion' and performs +completion when the user option `denote-history-completion-in-prompts' +is non-nil." + (and denote-history-completion-in-prompts + (memq fn denote-prompts-with-history-as-completion))) + +(defvar denote-ignore-region-in-denote-command nil + "If non-nil, the region is ignored by the `denote' command. + +The `denote' command uses the region as the default title when +prompted for a title. When this variable is non-nil, the +`denote' command ignores the region. This variable is useful in +commands that have their own way of handling the region.") + +(defvar denote-title-prompt-current-default nil + "Currently bound default title for `denote-title-prompt'. +Set the value of this variable within the lexical scope of a +command that needs to supply a default title before calling +`denote-title-prompt'.") + +(defun denote--command-with-features (command force-use-file-prompt-as-default-title force-ignore-region force-save in-background) + "Execute file-creating COMMAND with specified features. + +COMMAND is the symbol of a file-creating command to call, such as +`denote' or `denote-signature'. + +With non-nil FORCE-USE-FILE-PROMPT-AS-DEFAULT-TITLE, use the last +item of `denote-file-history' as the default title of the title +prompt. This is useful in a command such as `denote-link' where +the entry of the file prompt can be reused as the default title. + +With non-nil FORCE-IGNORE-REGION, the region is ignore when +creating the note, i.e. it will not be used as the initial title +in a title prompt. Else, the value of +`denote-ignore-region-in-denote-command' is respected. + +With non-nil FORCE-SAVE, the file is saved at the end of the note +creation. Else, the value of `denote-save-buffers' is respected. + +With non-nil IN-BACKGROUND, the note creation happens in the +background, i.e. the note's buffer will not be displayed after +the note is created. + +Note that if all parameters except COMMAND are nil, this is +equivalent to `(call-interactively command)'. + +The path of the newly created file is returned." + (let ((denote-save-buffers + (or force-save denote-save-buffers)) + (denote-ignore-region-in-denote-command + (or force-ignore-region denote-ignore-region-in-denote-command)) + (denote-title-prompt-current-default + (if force-use-file-prompt-as-default-title + denote-file-prompt-latest-input + denote-title-prompt-current-default)) + (path)) + (if in-background + (save-window-excursion + (setq path (call-interactively command))) + (setq path (call-interactively command))) + path)) + +(defun denote--handle-save-and-kill-buffer (mode file initial-state) + "Save and kill buffer of FILE according to MODE and INITIAL-STATE. + +The values of `denote-save-buffers' and `denote-kill-buffers' are +used to decide whether to save and/or kill the buffer visiting +FILE. + +MODE is one of the symbols `creation' or `rename'. + +INITIAL-STATE is nil or one of the following symbols: +`not-visited', `visited'. If a buffer was already visited at the +beginning of a rename operation, it is NOT killed automatically. + +If a buffer needs to be killed, it is also automatically saved, +no matter the value of `denote-save-buffers'." + (let* ((do-kill-buffer (and (not (eq initial-state 'visited)) + (or (eq denote-kill-buffers t) + (and (eq mode 'creation) + (eq denote-kill-buffers 'on-creation)) + (and (eq mode 'rename) + (eq denote-kill-buffers 'on-rename))))) + (do-save-buffer (or do-kill-buffer denote-save-buffers))) + (when-let* ((buffer (find-buffer-visiting file))) + (when do-save-buffer (with-current-buffer buffer (save-buffer))) + (when do-kill-buffer (kill-buffer buffer))))) + +(defvar denote-current-data nil + "Store the current unprocessed data passed to `denote'. +This is an alist where each `car' is one among `title', `keywords', +`signature', `directory', `date', `id', `file-type', `template'. The +value each of them contains is the unprocessed input (e.g. the title +before it is sluggified). + +This may be used by the hooks `denote-after-new-note-hook' and +`denote-after-rename-file-hook' to access the relevant data.") + +(defvar denote-use-title nil + "The title to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the title will always be the same +and the title prompt will be skipped.") + +(defvar denote-use-keywords 'default + "The keywords to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if `default'. + +Only ever `let' bind this, otherwise the keywords will always be the same +and the keywords prompt will be skipped.") + +(defvar denote-use-signature nil + "The signature to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the signaturew will always be the same +and the signature prompt will be skipped.") + +(defvar denote-use-file-type nil + "The title to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the file type will always be the +same.") + +(defvar denote-use-directory nil + "The directory to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the directory will always be the +same.") + +(defvar denote-use-date nil + "The date to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the date will always be the same +and the date prompt will be skipped.") + +(defvar denote-use-template nil + "The template to be used in a note creation command. +See the documentation of `denote' for acceptable values. This variable +is ignored if nil. + +Only ever `let' bind this, otherwise the template will always be the same +and the template prompt will be skipped.") + +(defun denote--creation-get-note-data-from-prompts () + "Retrieve the data necessary for note creation. + +The data elements are: title, keywords, file-type, directory, +date, template and signature. + +It is retrieved from prompts according to `denote-prompts' and +from `denote-use-*' variables. For example, if +`denote-use-title' is set to a title, then no prompts happen for +the title and the value of `denote-use-title' will be used +instead." + (let (title keywords file-type directory date template signature) + (dolist (prompt denote-prompts) + (pcase prompt + ('title (unless denote-use-title + (setq title (denote-title-prompt + (when (and (not denote-ignore-region-in-denote-command) + (use-region-p)) + (buffer-substring-no-properties + (region-beginning) + (region-end))))))) + ('keywords (when (eq denote-use-keywords 'default) + (setq keywords (denote-keywords-prompt)))) + ('file-type (unless denote-use-file-type + (setq file-type (denote-file-type-prompt)))) + ('subdirectory (unless denote-use-directory + (setq directory (denote-subdirectory-prompt)))) + ('date (unless denote-use-date + (setq date (denote-date-prompt)))) + ('template (unless denote-use-template + (setq template (denote-template-prompt)))) + ('signature (unless denote-use-signature + (setq signature (denote-signature-prompt)))))) + (list title keywords file-type directory date template signature))) + +(defun denote--creation-prepare-note-data (title keywords file-type directory date template signature) + "Return parameters in a valid form for file creation. + +The data is: TITLE, KEYWORDS, FILE-TYPE, DIRECTORY, DATE, +TEMPLATE and SIGNATURE. The identifier is also returned. + +If a `denote-use-*' variable is set for a data, its value is used +instead of that of the parameter." + (let* (;; Handle the `denote-use-*' variables + (title (or denote-use-title title)) + (keywords (if (eq denote-use-keywords 'default) keywords denote-use-keywords)) + (file-type (or denote-use-file-type file-type)) + (directory (or denote-use-directory directory)) + (date (or denote-use-date date)) + (template (or denote-use-template template)) + (signature (or denote-use-signature signature)) + ;; Make the data valid + (title (or title "")) + (file-type (denote--valid-file-type (or file-type denote-file-type))) + (keywords (denote-keywords-sort keywords)) + (date (denote-valid-date-p date)) + (date (cond (date date) + ((or (eq denote-generate-identifier-automatically t) + (eq denote-generate-identifier-automatically 'on-creation)) + (current-time)))) + (id (denote-get-identifier date)) + (id (if (string-empty-p id) id (denote--find-first-unused-id id))) + (date (if (string-empty-p id) nil (date-to-time id))) + (directory (if (and directory (denote--dir-in-denote-directory-p directory)) + (file-name-as-directory directory) + (denote-directory))) + (template (if (or (stringp template) (functionp template)) + template + (or (alist-get template denote-templates) ""))) + (signature (or signature ""))) + (list title keywords file-type directory date id template signature))) + +;;;###autoload +(defun denote (&optional title keywords file-type directory date template signature) + "Create a new note with the appropriate metadata and file name. + +Run the `denote-after-new-note-hook' after creating the new note and +return its path. Before returning the path, determine what needs to be +done to the buffer, in accordance with the user option `denote-kill-buffers'. + +When called interactively, the metadata and file name are prompted +according to the value of `denote-prompts'. + +When called from Lisp, all arguments are optional. + +- TITLE is a string or a function returning a string. + +- KEYWORDS is a list of strings. The list can be empty or the + value can be set to nil. + +- FILE-TYPE is a symbol among those described in the user option + `denote-file-type'. + +- DIRECTORY is a string representing the path to either the + value of the variable `denote-directory' or a subdirectory + thereof. The subdirectory must exist: Denote will not create + it. If DIRECTORY does not resolve to a valid path, the + variable `denote-directory' is used instead. + +- DATE is a string representing a date like 2022-06-30 or a date + and time like 2022-06-16 14:30. A nil value or an empty string + is interpreted as the `current-time'. + +- TEMPLATE is a symbol which represents the key of a cons cell in + the user option `denote-templates'. The value of that key is + inserted to the newly created buffer after the front matter. + +- SIGNATURE is a string or a function returning a string." + (interactive (denote--creation-get-note-data-from-prompts)) + (pcase-let* ((`(,title ,keywords ,file-type ,directory ,date ,id ,template ,signature) + (denote--creation-prepare-note-data title keywords file-type directory date template signature)) + (note-path (denote--prepare-note title keywords date id directory file-type template signature))) + (denote--keywords-add-to-history keywords) + (setq denote-current-data + (list + (cons 'title title) + (cons 'keywords keywords) + (cons 'signature signature) + (cons 'directory directory) + (cons 'date date) + (cons 'id id) + (cons 'file-type file-type) + (cons 'template template))) + (run-hooks 'denote-after-new-note-hook) + (denote--handle-save-and-kill-buffer 'creation note-path nil) + note-path)) + +(defvar denote-title-history nil + "Minibuffer history of `denote-title-prompt'.") + +(defalias 'denote--title-history 'denote-title-history + "Compatibility alias for `denote-title-history'.") + +(defmacro denote--with-conditional-completion (fn prompt history &optional initial-value default-value) + "Produce body of FN that may perform completion. +Use PROMPT, HISTORY, INITIAL-VALUE, and DEFAULT-VALUE as arguments for +the given minibuffer prompt." + `(if (denote--prompt-with-completion-p ,fn) + ;; NOTE 2023-10-27: By default SPC performs completion in the + ;; minibuffer. We do not want that, as the user should be able to + ;; input an arbitrary string, while still performing completion + ;; against their input history. + (minibuffer-with-setup-hook + (lambda () + (use-local-map + (let ((map (make-composed-keymap nil (current-local-map)))) + (define-key map (kbd "SPC") nil) + map))) + (completing-read ,prompt ,history nil nil ,initial-value ',history ,default-value)) + (read-string ,prompt ,initial-value ',history ,default-value))) + +(defun denote-title-prompt (&optional initial-title prompt-text) + "Prompt for title string. + +With optional INITIAL-TITLE use it as the initial minibuffer +text. With optional PROMPT-TEXT use it in the minibuffer instead +of the default prompt. + +Previous inputs at this prompt are available for minibuffer completion +if the user option `denote-history-completion-in-prompts' is set to a +non-nil value." + (denote--with-conditional-completion + 'denote-title-prompt + (format-prompt (or prompt-text "New file TITLE") denote-title-prompt-current-default) + denote-title-history + (or initial-title denote-title-prompt-current-default) + denote-title-prompt-current-default)) + +(defvar denote-file-type-history nil + "Minibuffer history of `denote-file-type-prompt'.") + +(defalias 'denote--file-type-history 'denote-file-type-history + "Compatibility alias for `denote-file-type-history'.") + +(defun denote-file-type-prompt () + "Prompt for variable `denote-file-type'. +Note that a non-nil value other than `text', `markdown-yaml', and +`markdown-toml' falls back to an Org file type. We use `org' +here for clarity." + (completing-read + "Select file TYPE: " (denote--file-type-keys) nil t + nil 'denote-file-type-history)) + +(defvar denote-date-history nil + "Minibuffer history of `denote-date-prompt'.") + +(defalias 'denote--date-history 'denote-date-history + "Compatibility alias for `denote-date-history'.") + +(declare-function org-read-date "org" (&optional with-time to-time from-string prompt default-time default-input inactive)) + +(defun denote--date-convert (date prefer-type) + "Determine how to convert DATE to PREFER-TYPE `:list' or `:string'." + (unless (memq prefer-type '(:list :string)) + (error "The PREFER-TYPE must be either `:list' or `:string'")) + (cond ((eq prefer-type :list) + date) + ((eq prefer-type :string) + (if date (format-time-string "%F %T" date) "")))) + +(defun denote-date-prompt (&optional initial-date prompt-text) + "Prompt for date, expecting YYYY-MM-DD or that plus HH:MM. +Use Org's more advanced date selection utility if the user option +`denote-date-prompt-use-org-read-date' is non-nil. + +With optional INITIAL-DATE use it as the initial minibuffer +text. With optional PROMPT-TEXT use it in the minibuffer instead +of the default prompt. + +INITIAL-DATE is a string that can be processed by `denote-valid-date-p', +a value that can be parsed by `decode-time' or nil." + (let ((initial-date (denote-valid-date-p initial-date))) + (if (and denote-date-prompt-use-org-read-date + (require 'org nil :no-error)) + (let* ((time (org-read-date nil t nil prompt-text (denote--date-convert initial-date :list))) + (org-time-seconds (format-time-string "%S" time)) + (cur-time-seconds (format-time-string "%S" (current-time)))) + ;; When the user does not input a time, org-read-date defaults to 00 for seconds. + ;; When the seconds are 00, we add the current seconds to avoid identifier collisions. + (when (string-equal "00" org-time-seconds) + (setq time (time-add time (string-to-number cur-time-seconds)))) + (format-time-string "%Y-%m-%d %H:%M:%S" time)) + (read-string + (or prompt-text "DATE and TIME for note (e.g. 2022-06-16 14:30): ") + (denote--date-convert initial-date :string) + 'denote-date-history)))) + +(defun denote-prompt-for-date-return-id (&optional initial-date prompt-text) + "Use `denote-date-prompt' and return it as `denote-id-format'. +Optional INITIAL-DATE and PROMPT-TEXT have the same meaning as +`denote-date-prompt'." + (denote-get-identifier + (denote-valid-date-p + (denote-date-prompt (denote-valid-date-p initial-date) prompt-text)))) + +(defvar denote-subdirectory-history nil + "Minibuffer history of `denote-subdirectory-prompt'.") + +(defalias 'denote--subdir-history 'denote-subdirectory-history + "Compatibility alias for `denote-subdirectory-history'.") + +;; Making it a completion table is useful for packages that read the +;; metadata, such as `marginalia' and `embark'. +(defun denote--subdirs-completion-table (dirs) + "Match DIRS as a completion table." + (let* ((def (car denote-subdirectory-history)) + (table (denote--completion-table 'file dirs)) + (prompt (format-prompt "Select SUBDIRECTORY" def))) + (completing-read prompt table nil t nil 'denote-subdirectory-history def))) + +(defun denote-subdirectory-prompt () + "Prompt for subdirectory of the variable `denote-directory'. +The table uses the `file' completion category (so it works with +packages such as `marginalia' and `embark')." + (let* ((root (directory-file-name (denote-directory))) + (subdirs (denote-directory-subdirectories)) + (dirs (push root subdirs))) + (denote--subdirs-completion-table dirs))) + +(defvar denote-template-history nil + "Minibuffer history of `denote-template-prompt'.") + +(defalias 'denote--template-history 'denote-template-history + "Compatibility alias for `denote-template-history'.") + +(defun denote-template-prompt () + "Prompt for template key in `denote-templates' and return its value." + (let ((templates denote-templates)) + (alist-get + (intern + (completing-read + "Select TEMPLATE key: " (mapcar #'car templates) + nil t nil 'denote-template-history)) + templates))) + +(defvar denote-signature-history nil + "Minibuffer history of `denote-signature-prompt'.") + +(defalias 'denote--signature-history 'denote-signature-history + "Compatibility alias for `denote-signature-history'.") + +(defun denote-signature-prompt (&optional initial-signature prompt-text) + "Prompt for signature string. +With optional INITIAL-SIGNATURE use it as the initial minibuffer +text. With optional PROMPT-TEXT use it in the minibuffer instead +of the default prompt. + +Previous inputs at this prompt are available for minibuffer completion +if the user option `denote-history-completion-in-prompts' is set to a +non-nil value." + (when (and initial-signature (string-empty-p initial-signature)) + (setq initial-signature nil)) + (denote--with-conditional-completion + 'denote-signature-prompt + (format-prompt (or prompt-text "New file SIGNATURE") nil) + denote-signature-history + initial-signature)) + +(defvar denote-files-matching-regexp-history nil + "Minibuffer history of `denote-files-matching-regexp-prompt'.") + +(defalias 'denote--files-matching-regexp-hist 'denote-files-matching-regexp-history + "Compatibility alias for `denote-files-matching-regexp-history'.") + +(defun denote-files-matching-regexp-prompt (&optional prompt-text) + "Prompt for REGEXP to filter Denote files by. +With optional PROMPT-TEXT use it instead of a generic prompt." + (denote--with-conditional-completion + 'denote-files-matching-regexp-prompt + (format-prompt (or prompt-text "Match files with the given REGEXP") nil) + denote-files-matching-regexp-history)) + +;;;;; Convenience commands as `denote' variants + +(defalias 'denote-create-note 'denote + "Alias for `denote' command.") + +(define-obsolete-function-alias + 'denote--add-prompts + 'denote-add-prompts + "3.0.0") + +(defun denote-add-prompts (additional-prompts) + "Add list of ADDITIONAL-PROMPTS to `denote-prompts'. +This is best done inside of a `let' to create a wrapper function around +`denote', `denote-rename-file', and generally any command that consults +the value of `denote-prompts'." + (seq-union additional-prompts denote-prompts)) + +;;;###autoload +(defun denote-type () + "Create note while prompting for a file type. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `file-type' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(file-type)))) + (call-interactively #'denote))) + +(defalias 'denote-create-note-using-type 'denote-type + "Alias for `denote-type' command.") + +;;;###autoload +(defun denote-date () + "Create note while prompting for a date. + +The date can be in YEAR-MONTH-DAY notation like 2022-06-30 or +that plus the time: 2022-06-16 14:30. When the user option +`denote-date-prompt-use-org-read-date' is non-nil, the date +prompt uses the more powerful Org+calendar system. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `date' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(date)))) + (call-interactively #'denote))) + +(defalias 'denote-create-note-using-date 'denote-date + "Alias for `denote-date' command.") + +;;;###autoload +(defun denote-subdirectory () + "Create note while prompting for a subdirectory. + +Available candidates include the value of the variable +`denote-directory' and any subdirectory thereof. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `subdirectory' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(subdirectory)))) + (call-interactively #'denote))) + +(defalias 'denote-create-note-in-subdirectory 'denote-subdirectory + "Alias for `denote-subdirectory' command.") + +;;;###autoload +(defun denote-template () + "Create note while prompting for a template. + +Available candidates include the keys in the `denote-templates' +alist. The value of the selected key is inserted in the newly +created note after the front matter. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `template' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(template)))) + (call-interactively #'denote))) + +(defalias 'denote-create-note-with-template 'denote-template + "Alias for `denote-template' command.") + +;;;###autoload +(defun denote-signature () + "Create note while prompting for a file signature. + +This is the equivalent of calling `denote' when `denote-prompts' +has the `signature' prompt appended to its existing prompts." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts (denote-add-prompts '(signature)))) + (call-interactively #'denote))) + +(defalias 'denote-create-note-using-signature 'denote-signature + "Alias for `denote-signature' command.") + +;;;###autoload +(defun denote-region () + "Call `denote' and insert therein the text of the active region. + +Note that, currently, `denote-save-buffers' and +`denote-kill-buffers' are NOT respected. The buffer is not +saved or killed at the end of `denote-region'." + (declare (interactive-only t)) + (interactive) + (if-let* (((region-active-p)) + ;; We capture the text early, otherwise it will be empty + ;; the moment `insert' is called. + (text (buffer-substring-no-properties (region-beginning) (region-end)))) + (progn + (let ((denote-ignore-region-in-denote-command t) + ;; FIXME: Find a way to insert the region before the buffer is + ;; saved/killed by the creation command. + (denote-save-buffers nil) + (denote-kill-buffers nil)) + (call-interactively 'denote)) + (push-mark (point)) + (insert text) + (run-hook-with-args 'denote-region-after-new-note-functions (mark) (point))) + (call-interactively 'denote))) + +;;;;; Other convenience commands + +;;;###autoload +(defun denote-open-or-create (target) + "Visit TARGET file in variable `denote-directory'. +If file does not exist, invoke `denote' to create a file. In that case, +use the last input at the file prompt as the default value of the title +prompt." + (interactive (list (denote-file-prompt nil "Select file (RET on no match to create it)" :no-require-match))) + (if (and target (file-exists-p target)) + (find-file target) + (denote--command-with-features #'denote :use-last-input-as-def-title nil nil nil))) + +;;;###autoload +(defun denote-open-or-create-with-command () + "Like `denote-open-or-create' but use one of the `denote-commands-for-new-notes'." + (declare (interactive-only t)) + (interactive) + (let ((target (denote-file-prompt nil "Select file (RET on no match to create it)" :no-require-match))) + (if (and target (file-exists-p target)) + (find-file target) + (denote--command-with-features (denote-command-prompt) :use-file-prompt-as-def-title nil nil nil)))) + +;;;; Note modification + +;;;;; Common helpers for note modifications + +(defun denote--file-types-with-extension (extension) + "Return only the entries of `denote-file-types' with EXTENSION. +See the format of `denote-file-types'." + (seq-filter (lambda (type) + (string-equal (plist-get (cdr type) :extension) extension)) + denote-file-types)) + +(defun denote--file-type-org-extra-p () + "Return non-nil if this is an `org-capture' or Org Note buffer." + (and (derived-mode-p 'org-mode) + (or (and (bound-and-true-p org-capture-mode) + (string-match-p "\\`CAPTURE-.*" (buffer-name))) + (string-match-p "\\`\\*Org Note\\*" (buffer-name)) + (null buffer-file-name)))) + +(defun denote-file-type (file) + "Use the file extension to detect the file type of FILE. + +If more than one file type correspond to this file extension, use the +first file type for which the :title-key-regexp in `denote-file-types' +matches in the file. + +Return nil if the file type is not recognized." + (when-let* ((extension (denote-get-file-extension-sans-encryption file)) + (types (denote--file-types-with-extension extension))) + (if (= (length types) 1) + (caar types) + (or (car (seq-find + (lambda (type) + (denote--regexp-in-file-p (plist-get (cdr type) :title-key-regexp) file)) + types)) + (caar types))))) + +(defun denote-filetype-heuristics (file) + "Return likely file type of FILE. +If in the process of `org-capture', consider the file type to be that of +Org. Otherwise, use the function `denote-file-type' to return the type." + (if (denote--file-type-org-extra-p) + 'org + (denote-file-type file))) + +(defun denote--revert-dired (buf) + "Revert BUF if appropriate. +Do it if BUF is in Dired mode and is either part of the variable +`denote-directory' or the `current-buffer'." + (let ((current (current-buffer))) + (with-current-buffer buf + (when (and (eq major-mode 'dired-mode) + (or (and default-directory (denote--dir-in-denote-directory-p default-directory)) + (eq current buf))) + (revert-buffer))))) + +(defun denote-update-dired-buffers () + "Update Dired buffers of variable `denote-directory'. +Also revert the current Dired buffer even if it is not inside the +variable `denote-directory'." + (mapc #'denote--revert-dired (buffer-list))) + +(defun denote-rename-file-and-buffer (old-name new-name) + "Rename file named OLD-NAME to NEW-NAME, updating buffer name. + +If the file exists on the file system, it is renamed. This +function may be called when creating a new note and the file does +not exist yet. + +If a buffer is visiting the file, its name is updated." + (unless (string= (expand-file-name old-name) (expand-file-name new-name)) + (when (and (file-regular-p old-name) + (file-writable-p new-name)) + (cond + ((derived-mode-p 'dired-mode) + (dired-rename-file old-name new-name nil)) + ;; NOTE 2024-02-25: The `vc-rename-file' requires the file to be + ;; saved, but our convention is to not save the buffer after + ;; changing front matter unless we absolutely have to (allows + ;; users to do `diff-buffer-with-file', for example). + ((and denote-save-buffers (not (buffer-modified-p)) (vc-backend old-name)) + (vc-rename-file old-name new-name)) + (t + (rename-file old-name new-name nil)))) + (when-let* ((buffer (find-buffer-visiting old-name))) + (with-current-buffer buffer + (set-visited-file-name new-name nil t))))) + +(define-obsolete-function-alias + 'denote--add-front-matter + 'denote-prepend-front-matter + "4.0.0") + +(defun denote-prepend-front-matter (file title keywords signature date id file-type) + "Prepend front matter to FILE. +The TITLE, KEYWORDS, DATE, ID, SIGNATURE, and FILE-TYPE are passed from +the renaming command and are used to construct a new front matter block +if appropriate." + (when-let* ((new-front-matter (denote--format-front-matter title date keywords id signature file-type))) + (with-current-buffer (find-file-noselect file) + (goto-char (point-min)) + (insert new-front-matter)))) + +(defun denote--regexp-in-file-p (regexp file) + "Return t if REGEXP matches in the FILE." + (denote--file-with-temp-buffer file + (re-search-forward regexp nil t 1))) + +(defun denote-rewrite-keywords (file keywords file-type &optional save-buffer) + "Rewrite KEYWORDS in FILE outright according to FILE-TYPE. + +Do the same as `denote-rewrite-front-matter' for keywords, +but do not ask for confirmation. + +With optional SAVE-BUFFER, save the buffer corresponding to FILE. + +This function is for use in the commands `denote-keywords-add', +`denote-keywords-remove', `denote-dired-rename-files', or +related." + (let* ((new-front-matter (denote--format-front-matter "" (current-time) keywords "" "" file-type)) + (new-keywords-line (denote--retrieve-front-matter-keywords-line-from-content new-front-matter file-type))) + (with-current-buffer (find-file-noselect file) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) + (goto-char (line-beginning-position)) + (insert new-keywords-line) + (delete-region (point) (line-end-position)) + (when save-buffer (save-buffer)))))))) + +(defun denote--component-has-value-p (component value) + "Return non-nil if COMPONENT has a non-nil/non-empty VALUE. + +COMPONENT can be one of `title', `keywords', `signature', `date', +`identifier'. + +VALUE is the corresponding value to test. + +This function returns nil given an empty string title, signature or +identifier. It also returns nil given a nil date or nil keywords." + (pcase component + ('title (not (string-empty-p value))) + ('keywords (not (null value))) + ('signature (not (string-empty-p (denote-sluggify-signature value)))) + ('date (not (null value))) + ('identifier (not (string-empty-p value))))) + +(defun denote--get-old-and-new-front-matter-lines (file new-front-matter file-type) + "Return an alist of the old and new front-matter lines for each component. + +The FILE contains the old front matter lines. + +NEW-FRONT-MATTER is a the front matter with the new values, with the +format given by FILE-TYPE." + `((title . ((old . ,(denote-retrieve-front-matter-title-line file file-type)) + (new . ,(denote--retrieve-front-matter-title-line-from-content new-front-matter file-type)))) + (keywords . ((old . ,(denote-retrieve-front-matter-keywords-line file file-type)) + (new . ,(denote--retrieve-front-matter-keywords-line-from-content new-front-matter file-type)))) + (signature . ((old . ,(denote-retrieve-front-matter-signature-line file file-type)) + (new . ,(denote--retrieve-front-matter-signature-line-from-content new-front-matter file-type)))) + (date . ((old . ,(denote-retrieve-front-matter-date-line file file-type)) + (new . ,(denote--retrieve-front-matter-date-line-from-content new-front-matter file-type)))) + (identifier . ((old . ,(denote-retrieve-front-matter-identifier-line file file-type)) + (new . ,(denote--retrieve-front-matter-identifier-line-from-content new-front-matter file-type)))))) + +(defun denote--get-front-matter-components-order (content file-type) + "Return the components in the order they appear in CONTENT given FILE-TYPE. + +Return a list containing the symbols `title', `signature', `keywords', +`identifier' and `date' in the order that they appear in TEXT. TEXT can +be any string. For example, it can be a front matter template or an +entire file content." + (let ((components-with-line-numbers '())) + (with-temp-buffer + (insert content) + (goto-char (point-min)) + (when (re-search-forward (denote--title-key-regexp file-type) nil t 1) + (push `(,(line-number-at-pos) . title) components-with-line-numbers)) + (goto-char (point-min)) + (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) + (push `(,(line-number-at-pos) . keywords) components-with-line-numbers)) + (goto-char (point-min)) + (when (re-search-forward (denote--signature-key-regexp file-type) nil t 1) + (push `(,(line-number-at-pos) . signature) components-with-line-numbers)) + (goto-char (point-min)) + (when (re-search-forward (denote--date-key-regexp file-type) nil t 1) + (push `(,(line-number-at-pos) . date) components-with-line-numbers)) + (goto-char (point-min)) + (when (re-search-forward (denote--identifier-key-regexp file-type) nil t 1) + (push `(,(line-number-at-pos) . identifier) components-with-line-numbers))) + (mapcar #'cdr + (sort components-with-line-numbers (lambda (x y) (< (car x) (car y))))))) + +(defun denote--file-has-front-matter-p (file file-type) + "Return non-nil if FILE has at least one front-matter line, given FILE-TYPE. + +This is checked against its front matter definition. If the front matter +definition has no lines, this function returns non-nil." + (let* ((front-matter (denote--front-matter file-type)) + (file-content (with-current-buffer (find-file-noselect file) (buffer-string))) + (components-in-template (denote--get-front-matter-components-order front-matter file-type)) + (components-in-file (denote--get-front-matter-components-order file-content file-type))) + (or (null components-in-template) + (seq-intersection components-in-template components-in-file)))) + +(defun denote--get-front-matter-rewrite-prompt (final-components to-add to-remove to-modify old-and-new-front-matter-lines) + "Return the prompt for the front matter rewrite operation. + +FINAL-COMPONENTS is the list of components to handle at the end of the +rewrite operation. + +TO-ADD, TO-REMOVE, and TO-MODIFY are the list of components that needs +to be added, removed or modified. + +OLD-AND-NEW-FRONT-MATTER-LINES is an alist containing the old and new +front matter lines." + (let ((prompt "Replace front matter?")) + (dolist (component final-components) + (let ((old-line (alist-get 'old (alist-get component old-and-new-front-matter-lines))) + (new-line (alist-get 'new (alist-get component old-and-new-front-matter-lines))) + (next-prompt "")) + (cond ((memq component to-remove) + (setq next-prompt (format "\n-%s\n" + (propertize old-line 'face 'denote-faces-prompt-old-name)))) + ((memq component to-add) + (setq next-prompt (format "\n-%s\n" + (propertize new-line 'face 'denote-faces-prompt-new-name)))) + ((memq component to-modify) + (setq next-prompt (format "\n-%s\n-%s\n" + (propertize old-line 'face 'denote-faces-prompt-old-name) + (propertize new-line 'face 'denote-faces-prompt-new-name))))) + (setq prompt (concat prompt next-prompt)))) + (concat prompt "?"))) + +(defun denote--get-final-components-for-rewrite (components-in-file components-in-template components-to-add) + "Return the final components to handle by a front matter rewrite operation. + +COMPONENTS-TO-ADD is the list of components that have to be added to +COMPONENTS-IN-FILE to build the list of components that will need to be +handled during a front matter rewrite operation. + +COMPONENTS-IN-TEMPLATE is the list of components in a front matter +template. They are used to determine how the COMPONENTS-TO-ADD are +added to COMPONENTS-IN-FILE. + +Example: + file = (title signature) + template = (title keywords date id signature) + +The date line is missing from the file. From the template, we find out +that it needs to be added *after* a keywords line. Since we don't have +one in the file, we keep looking for a line to add it *after* and find a +title line. Had we not found the title line in the file, we would have +searched for a line to insert it *before*. We would have inserted the +date line before the signature line, for example. + +This is repeated until all missing components are added." + (let ((final-components (copy-sequence components-in-file))) + (dolist (component components-to-add) + (if-let* ((previous-components-in-template + (seq-take-while (lambda (x) (not (eq x component))) components-in-template)) + (first-previous-component-in-file + (seq-find (lambda (x) (memq x final-components)) (reverse previous-components-in-template)))) + ;; Insert after the existing element. + (let ((sublist final-components)) + (while sublist + (if (not (eq (car sublist) first-previous-component-in-file)) + (setq sublist (cdr sublist)) + (push component (cdr sublist)) + (setq sublist nil)))) + (let* ((next-components-in-template + (cdr (seq-drop-while (lambda (x) (not (eq x component))) components-in-template))) + (first-next-component-in-file + (seq-find (lambda (x) (memq x final-components)) next-components-in-template))) + ;; Insert before the existing element. The intention is to + ;; modify final-components, but it does not work when push + ;; is called on sublist on the first iteration of the loop. + (if (eq (car final-components) first-next-component-in-file) + (push component final-components) + (let ((sublist final-components)) + (while sublist + (if (not (eq (car sublist) first-next-component-in-file)) + (setq sublist (cdr sublist)) + (push component sublist) + (setq sublist nil)))))))) + final-components)) + +(defun denote-rewrite-front-matter (file title keywords signature date identifier file-type) + "Rewrite front matter of note after `denote-rename-file'. +The FILE, TITLE, KEYWORDS, SIGNATURE, DATE, IDENTIFIER, and FILE-TYPE +are given by the renaming command and are used to construct new front +matter values if appropriate. + +If `denote-rename-confirmations' contains `rewrite-front-matter', +prompt to confirm the rewriting of the front matter." + (let* ((front-matter (denote--front-matter file-type)) + (file-content (with-current-buffer (find-file-noselect file) (buffer-string))) + (components-in-template (denote--get-front-matter-components-order front-matter file-type)) + (components-in-file (denote--get-front-matter-components-order file-content file-type)) + (components-to-add '()) + (components-to-remove '()) + (components-to-modify '()) + (new-front-matter (denote--format-front-matter title date keywords identifier signature file-type)) + (old-and-new-front-matter-lines (denote--get-old-and-new-front-matter-lines file new-front-matter file-type))) + ;; Build the lists of components to add, remove, modify. + (dolist (component '(title keywords signature identifier date)) + ;; Ignore the component if it is not in the template. It is not added, removed or modified. + (when (memq component components-in-template) + (let ((value (pcase component ('title title) ('keywords keywords) ('signature signature) ('date date) ('identifier identifier)))) + (cond ((and (not (memq component components-in-file)) + (denote--component-has-value-p component value)) + (push component components-to-add)) + ((and (memq component components-in-file) + ;; The component can still be marked for modification. + (not (memq component denote-front-matter-components-present-even-if-empty-value)) + (not (denote--component-has-value-p component value))) + (push component components-to-remove)) + ((and (memq component components-in-file) + (not (string= (alist-get 'old (alist-get component old-and-new-front-matter-lines)) + (alist-get 'new (alist-get component old-and-new-front-matter-lines))))) + (push component components-to-modify)))))) + ;; There should be at least one component in the file and the template. + (when (and (seq-intersection components-in-file components-in-template) + (or components-to-add components-to-remove components-to-modify)) + (when-let* ((final-components (denote--get-final-components-for-rewrite + components-in-file components-in-template components-to-add))) + (with-current-buffer (find-file-noselect file) + (when (or (not (memq 'rewrite-front-matter denote-rename-confirmations)) + (y-or-n-p (denote--get-front-matter-rewrite-prompt + final-components + components-to-add components-to-remove components-to-modify + old-and-new-front-matter-lines))) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + ;; Position point at the beginning of the first front matter line + (let ((first-component (car (seq-difference final-components components-to-add)))) + (re-search-forward + (funcall (denote--get-component-key-regexp-function first-component) file-type) nil t 1) + (goto-char (line-beginning-position))) + ;; Do the modifications + (dolist (component final-components) + (let ((component-key-regexp-function (denote--get-component-key-regexp-function component)) + (new-line (alist-get 'new (alist-get component old-and-new-front-matter-lines)))) + (cond ((memq component components-to-remove) + (re-search-forward (funcall component-key-regexp-function file-type) nil t 1) + (delete-region (line-beginning-position) (line-beginning-position 2))) + ((memq component components-to-add) + (insert (concat new-line "\n"))) + ((memq component components-to-modify) + (re-search-forward (funcall component-key-regexp-function file-type) nil t 1) + (goto-char (line-beginning-position)) + (insert new-line) + (delete-region (point) (line-end-position)) + (goto-char (line-beginning-position 2))) + (t + (goto-char (line-beginning-position 2)))))))))))))) + +;;;;; The renaming commands and their prompts + +(defun denote--rename-dired-file-or-current-file-or-prompt () + "Return Dired file at point or the current file, else prompt for one. +Throw error if FILE is not regular, else return FILE." + (or (dired-get-filename nil t) + buffer-file-name + (let* ((file (buffer-file-name)) + (format (if file + (format "Rename FILE Denote-style [%s]: " file) + "Rename FILE Denote-style: ")) + (selected-file (read-file-name format nil file t nil))) + (if (or (file-directory-p selected-file) + (not (file-regular-p selected-file))) + (user-error "Only rename regular files") + selected-file)))) + +(defun denote-rename-file-prompt (old-name new-name) + "Prompt to rename file named OLD-NAME to NEW-NAME. +Return non-nil if the file should be renamed. + +If `denote-rename-confirmations' does not contain +`modify-file-name', return t without prompting." + (or (not (memq 'modify-file-name denote-rename-confirmations)) + (unless (string= (expand-file-name old-name) (expand-file-name new-name)) + (y-or-n-p + (format "Rename %s to %s?" + (propertize (file-name-nondirectory old-name) 'face 'denote-faces-prompt-old-name) + (propertize (file-name-nondirectory new-name) 'face 'denote-faces-prompt-new-name)))))) + +(defun denote-add-front-matter-prompt (file) + "Prompt to add a front-matter to FILE. +Return non-nil if a new front matter should be added. + +If `denote-rename-confirmations' does not contain +`add-front-matter', return t without prompting." + (or (not (memq 'add-front-matter denote-rename-confirmations)) + (y-or-n-p + (format "Add new front matter to %s?" + (propertize (file-name-nondirectory file) 'face 'denote-faces-prompt-new-name))))) + +;; NOTE 2023-10-20: We do not need a user option for this, though it +;; can be useful to have it as a variable. +(defvar denote-rename-max-mini-window-height 0.33 + "How much to enlarge `max-mini-window-height' for renaming operations.") + +(defun denote--generate-date-for-rename (file) + "Generate a date for FILE. + +Respect `denote-generate-identifier-automatically'." + (if (or (eq denote-generate-identifier-automatically t) + (eq denote-generate-identifier-automatically 'on-rename)) + (or (file-attribute-modification-time (file-attributes file)) + (current-time)) + nil)) + +(defun denote--rename-file (file title keywords signature date) + "Rename FILE according to the other parameters. +Parameters TITLE, KEYWORDS, SIGNATURE and DATE are as described +in `denote-rename-file' and are assumed to be valid (TITLE and +SIGNATURE are strings, KEYWORDS is a list, etc.). + +This function only does the work necessary to rename a file +according to its parameters. In particular, it does not prompt +for anything. It is meant to be combined with +`denote--rename-get-file-info-from-prompts-or-existing' to create +a renaming command. + +Respect `denote-rename-confirmations', `denote-save-buffers' and +`denote-kill-buffers'." + (let* ((initial-state (if (find-buffer-visiting file) 'visited 'not-visited)) + (file-type (denote-filetype-heuristics file)) + (keywords (denote-keywords-sort keywords)) + (directory (file-name-directory file)) + (extension (denote-get-file-extension file)) + (date (or date (denote--generate-date-for-rename file))) + (old-id (or (denote-retrieve-filename-identifier file) "")) + (id (denote-get-identifier date)) + (id (cond ((or (string-empty-p id) (string= old-id id)) + id) + ((and (not (string-empty-p old-id)) (denote--file-has-backlinks-p file)) + (user-error "The date cannot be modified because the file has backlinks")) + (t + (denote--find-first-unused-id id)))) + (date (if (string-empty-p id) nil (date-to-time id))) + (new-name (denote-format-file-name directory id keywords title extension signature)) + (max-mini-window-height denote-rename-max-mini-window-height)) + (when (and (file-regular-p new-name) + (not (string= (expand-file-name file) (expand-file-name new-name)))) + (user-error "The destination file `%s' already exists" new-name)) + ;; Modify file name, buffer name, or both + (when (denote-rename-file-prompt file new-name) + (denote-rename-file-and-buffer file new-name)) + ;; Handle front matter if new-name is of a supported type (rewrite or add front matter) + (when (and (denote-file-has-supported-extension-p file) + (denote-file-is-writable-and-supported-p new-name)) + (if (denote--file-has-front-matter-p new-name file-type) + (denote-rewrite-front-matter new-name title keywords signature date id file-type) + (when (denote-add-front-matter-prompt new-name) + (denote-prepend-front-matter new-name title keywords signature date id file-type)))) + (when (and denote--used-ids (not (string-empty-p id))) + (puthash id t denote--used-ids)) + (denote--handle-save-and-kill-buffer 'rename new-name initial-state) + (setq denote-current-data + (list + (cons 'title title) + (cons 'keywords keywords) + (cons 'signature signature) + (cons 'directory directory) + (cons 'date date) + (cons 'id id) + (cons 'file-type file-type) + (cons 'template ""))) + (run-hooks 'denote-after-rename-file-hook) + new-name)) + +(defun denote--rename-get-file-info-from-prompts-or-existing (file) + "Retrieve existing info from FILE and prompt according to `denote-prompts'. + +It is meant to be combined with `denote--rename-file' to create +renaming commands." + (let* ((file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)) + (file-type (denote-filetype-heuristics file)) + (id (or (denote-retrieve-filename-identifier file) "")) + (date (or (denote-valid-date-p id) (denote--generate-date-for-rename file))) + (title (or (denote-retrieve-title-or-filename file file-type) "")) + (keywords (denote-extract-keywords-from-path file)) + (signature (or (denote-retrieve-filename-signature file) ""))) + (dolist (prompt denote-prompts) + (pcase prompt + ('title + (setq title (denote-title-prompt + title + (format "Rename `%s' with TITLE (empty to remove)" file-in-prompt)))) + ('keywords + (setq keywords (denote-keywords-prompt + (format "Rename `%s' with KEYWORDS (empty to remove)" file-in-prompt) + (string-join keywords ",")))) + ('signature + (setq signature (denote-signature-prompt + signature + (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))) + ('date + (setq date (denote-valid-date-p (denote-date-prompt + date + (format "Rename `%s' with DATE" file-in-prompt))))))) + (list title keywords signature date))) + +;;;###autoload +(defun denote-rename-file (file title keywords signature date) + "Rename file and update existing front matter if appropriate. + +Always rename the file where it is located in the file system: +never move it to another directory. + +If in Dired, consider FILE to be the one at point, else the +current file, else prompt with minibuffer completion for one. +When called from Lisp, FILE is a file system path represented as +a string. + +If FILE has a Denote-compliant identifier, retain it while +updating components of the file name referenced by the user +option `denote-prompts'. By default, these are the TITLE and +KEYWORDS. The SIGNATURE is another one. When called from Lisp, +TITLE and SIGNATURE are strings, while KEYWORDS is a list of +strings. + +If there is no identifier, create an identifier based on the +following conditions: + +1. If the `denote-prompts' includes an entry for date prompts, + then prompt for DATE and take its input to produce a new + identifier. For use in Lisp, DATE must conform with + `denote-valid-date-p'. + +2. If DATE is nil (e.g. when `denote-prompts' does not include a + date entry), use the file attributes to determine the last + modified date of FILE and format it as an identifier. + +3. As a fallback, derive an identifier from the current date and + time. + +4. At any rate, if the resulting identifier is not unique among + the files in the variable `denote-directory', increment it + such that it becomes unique. + +In interactive use, and assuming `denote-prompts' includes a +title entry, make the TITLE prompt have prefilled text in the +minibuffer that consists of the current title of FILE. The +current title is either retrieved from the front matter (such as +the #+title in Org) or from the file name. + +Do the same for the SIGNATURE prompt, subject to `denote-prompts', +by prefilling the minibuffer with the current signature of FILE, +if any. + +Same principle for the KEYWORDS prompt: convert the keywords in +the file name into a comma-separated string and prefill the +minibuffer with it (the KEYWORDS prompt accepts more than one +keywords, each separated by a comma, else the `crm-separator'). + +For all prompts, interpret an empty input as an instruction to +remove that file name component. For example, if a TITLE prompt +is available and FILE is 20240211T093531--some-title__keyword1.org +then rename FILE to 20240211T093531__keyword1.org. + +In interactive use, if there is no entry for a file name +component in `denote-prompts', keep it as-is. + +When called from Lisp, the special symbol `keep-current' can be +used for the TITLE, KEYWORDS, SIGNATURE and DATE parameters to +keep them as-is. + +[ NOTE: Please check with your minibuffer user interface how to + provide an empty input. The Emacs default setup accepts the + empty minibuffer contents as they are, though popular packages + like `vertico' use the first available completion candidate + instead. For `vertico', the user must either move one up to + select the prompt and then type RET there with empty contents, + or use the command `vertico-exit-input' with empty contents. + That Vertico command is bound to M-RET as of this writing on + 2024-02-13 08:08 +0200. ] + +As a final step, ask for confirmation, showing the difference +between old and new file names. Do not ask for confirmation if +the user option `denote-rename-confirmations' does not contain +the symbol `modify-file-name'. + +If FILE has front matter for TITLE and KEYWORDS, ask to rewrite +their values in order to reflect the new input, unless +`denote-rename-confirmations' lacks `rewrite-front-matter'. When +the `denote-save-buffers' is nil (the default), do not save the +underlying buffer, thus giving the user the option to +double-check the result, such as by invoking the command +`diff-buffer-with-file'. The rewrite of the TITLE and KEYWORDS +in the front matter should not affect the rest of the front +matter. + +If the file does not have front matter but is among the supported file +types (per the user option `denote-file-type'), add front matter to the +top of it and leave the buffer unsaved for further inspection. Save the +buffer if `denote-save-buffers' is non-nil. + +When `denote-kill-buffers' is t or `on-rename', kill the buffer +if it was not already being visited before the rename operation. + +For the front matter of each file type, refer to the variables: + +- `denote-org-front-matter' +- `denote-text-front-matter' +- `denote-toml-front-matter' +- `denote-yaml-front-matter' + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +Run the `denote-after-rename-file-hook' after renaming FILE. + +This command is intended to (i) rename Denote files, (ii) convert +existing supported file types to Denote notes, and (ii) rename +non-note files (e.g. PDF) that can benefit from Denote's +file-naming scheme. + +For a version of this command that works with multiple files +one-by-one, use `denote-dired-rename-files'." + (interactive + (let* ((file (denote--rename-dired-file-or-current-file-or-prompt))) + (append (list file) (denote--rename-get-file-info-from-prompts-or-existing file)))) + (let* ((file-type (denote-filetype-heuristics file)) + (title (if (eq title 'keep-current) + (or (denote-retrieve-title-or-filename file file-type) "") + title)) + (keywords (if (eq keywords 'keep-current) + (denote-extract-keywords-from-path file) + keywords)) + (signature (if (eq signature 'keep-current) + (or (denote-retrieve-filename-signature file) "") + signature)) + (date (if (eq date 'keep-current) + (denote-retrieve-filename-identifier file) + date)) + ;; Make the data valid + (date (denote-valid-date-p date)) + (new-name (denote--rename-file file title keywords signature date))) + (denote-update-dired-buffers) + new-name)) + +(defun denote-rename-file-title () + "Convenience command to change the title of a file. +Like `denote-rename-file', but prompts only for the title. + +Add or remove a title in one go. Do this by prepopulating the +minibuffer prompt with the existing title. The user can then modify it +accordingly. An empty input means to remove the title altogether. + +Please check the documentation of `denote-rename-file' with regard to +how a completion User Interface may accept an empty input." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(title))) + (call-interactively #'denote-rename-file))) + +(defun denote-rename-file-keywords () + "Convenience command to change the keywords of a file. +Like `denote-rename-file', but prompts only for keywords. + +Add or remove keywords in one go. Do this by prepopulating the +minibuffer prompt with the existing keywords. The user can then insert +the `crm-separator' (normally a comma), to write new keywords or edit +what is in the prompt to rewrite them accordingly. An empty input means +to remove all keywords. + +Please check the documentation of `denote-rename-file' with regard to +how a completion User Interface may accept an empty input." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(keywords))) + (call-interactively #'denote-rename-file))) + +(defun denote-rename-file-date () + "Convenience command to change the date of a file. +Like `denote-rename-file', but prompts only for the date. + +Modify a date in one go." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(date))) + (call-interactively #'denote-rename-file))) + +(define-obsolete-function-alias 'denote-keywords-add 'denote-rename-file-keywords "3.0.0") +(define-obsolete-function-alias 'denote-rename-add-keywords 'denote-rename-file-keywords "3.0.0") +(define-obsolete-function-alias 'denote-keywords-remove 'denote-rename-file-keywords "3.0.0") +(define-obsolete-function-alias 'denote-rename-rename-keywords 'denote-rename-file-keywords "3.0.0") + +(defun denote-rename-file-signature () + "Convenience command to change the signature of a file. +Like `denote-rename-file', but prompts only for the signature. + +Add or remove a signature in one go. Do this by prepopulating the +minibuffer prompt with the existing signature. The user can then modify +it accordingly. An empty input means to remove the signature +altogether. + +Please check the documentation of `denote-rename-file' with regard to +how a completion User Interface may accept an empty input." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(signature))) + (call-interactively #'denote-rename-file))) + +(define-obsolete-function-alias 'denote-add-signature 'denote-rename-file-signature "3.0.0") +(define-obsolete-function-alias 'denote-remove-signature 'denote-rename-file-signature "3.0.0") + +;;;###autoload +(defun denote-dired-rename-files () + "Rename Dired marked files same way as `denote-rename-file'. +Rename each file in sequence, making all the relevant prompts. +Unlike `denote-rename-file', do not prompt for confirmation of +the changes made to the file: perform them outright (same as +setting `denote-rename-confirmations' to a nil value)." + (declare (interactive-only t)) + (interactive nil dired-mode) + (let ((denote--used-ids (denote--get-all-used-ids)) + (denote-rename-confirmations nil)) + (if-let* ((marks (dired-get-marked-files))) + (progn + (dolist (file marks) + (pcase-let ((`(,title ,keywords ,signature ,date) + (denote--rename-get-file-info-from-prompts-or-existing file))) + (denote--rename-file file title keywords signature date))) + (denote-update-dired-buffers)) + (user-error "No marked files; aborting")))) + +(defalias 'denote-dired-rename-marked-files 'denote-dired-rename-files + "Alias for `denote-dired-rename-files'.") + +(defun denote-keywords--combine (combination-type user-input-keywords keywords) + "COMBINATION-TYPE is either `:add', `:remove' or `:replace'. + +USER-INPUT-KEYWORDS are new keywords collected from the end-user. +KEYWORDS are the existing keywords for the underlying file. + +This function is an internal implementation function." + (cond + ((eq combination-type :add) + (seq-union keywords user-input-keywords)) + ((eq combination-type :replace) + user-input-keywords) + ((eq combination-type :remove) + (seq-difference keywords user-input-keywords)) + (t + (error "Unknown operation in denote-keywords--combine: %s" + combination-type)))) + +(defun denote-dired-rename-marked-files--change-keywords (combination-type keywords-prompt) + "COMBINATION-TYPE is either `:add', `:remove' or `:replace'. + +KEYWORDS-PROMPT is the prompt we show the end-user, when taking keywords +as input. + +This function is an internal implementation function." + (if-let* ((marks (dired-get-marked-files))) + (let ((denote-prompts '()) + (denote-rename-confirmations nil) + (user-input-keywords (denote-keywords-prompt keywords-prompt)) + (denote--used-ids (denote--get-all-used-ids))) + (dolist (file marks) + (pcase-let* ((`(,title ,keywords ,signature ,date) + (denote--rename-get-file-info-from-prompts-or-existing file)) + (new-keywords (denote-keywords-sort (denote-keywords--combine combination-type user-input-keywords keywords)))) + (denote--rename-file file title new-keywords signature date))) + (denote-update-dired-buffers)) + (user-error "No marked files; aborting"))) + +;;;###autoload +(defun denote-dired-rename-marked-files-with-keywords () + "Rename marked files in Dired to a Denote file name by writing keywords. + +Specifically, do the following: + +- retain the file's existing name and make it the TITLE field, + per Denote's file-naming scheme; + +- sluggify the TITLE, according to our conventions (check the + user option `denote-file-name-slug-functions'); + +- prepend an identifier to the TITLE; + +- preserve the file's extension, if any; + +- prompt once for KEYWORDS and apply the user's input to the + corresponding field in the file name, rewriting any keywords + that may exist while removing keywords that do exist if + KEYWORDS is empty; + +- add or rewrite existing front matter to the underlying file, if it is + recognized as a Denote note (per the user option `denote-file-type'), + such that it includes the new keywords. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'. + +Run the `denote-after-rename-file-hook' after renaming is done. + +Also see the specialized commands to only add or remove keywords: + +- `denote-dired-rename-marked-files-add-keywords'. +- `denote-dired-rename-marked-files-remove-keywords'." + (declare (interactive-only t)) + (interactive nil dired-mode) + (denote-dired-rename-marked-files--change-keywords + :replace "Rename marked files with KEYWORDS, overwriting existing (empty to ignore/remove)")) + +;;;###autoload +(defun denote-dired-rename-marked-files-add-keywords () + "Like `denote-dired-rename-marked-files-with-keywords' to only add keywords." + (declare (interactive-only t)) + (interactive nil dired-mode) + (denote-dired-rename-marked-files--change-keywords + :add "Add KEYWORDS to marked files")) + +;;;###autoload +(defun denote-dired-rename-marked-files-remove-keywords () + "Like `denote-dired-rename-marked-files-with-keywords' to only remove keywords." + (declare (interactive-only t)) + (interactive nil dired-mode) + (denote-dired-rename-marked-files--change-keywords + :remove "Remove KEYWORDS from marked files")) + +;;;###autoload +(defun denote-rename-file-using-front-matter (file) + "Rename FILE using its front matter as input. +When called interactively, FILE is the variable `buffer-file-name' or +the Dired file at point, which is subsequently inspected for the +requisite front matter. It is thus implied that the FILE has a file +type that is supported by Denote, per the user option `denote-file-type'. + +The values of `denote-rename-confirmations', +`denote-save-buffers' and `denote-kill-buffers' are respected. + +Only the front matter lines that appear in the front matter template (as +defined in `denote-file-types') will be handled. + +To change the identifier (date) of the note with this command, the +identifier line (if present) of the front matter must be modified. +Modifying the date line has no effect. + +While this command generally does not modify the front matter, there are +exceptions. The value of the `date' line will follow that of the +`identifier' line. If they are both in the front matter template and +the `date' line is missing, it will be added again. Similarly, if they +are both in the front matter template and the `date' line is present and +the `identifier' line has been removed, the `date' line will be removed +as well. Also, if the keywords are out of order and +`denote-sort-keywords' is non-nil, they will be sorted. There will be a +prompt for this if `denote-rename-confirmations' contains +`rewrite-front-matter'. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'." + (interactive (list (or (dired-get-filename nil t) buffer-file-name))) + (unless (denote-file-is-writable-and-supported-p file) + (user-error "The file is not writable or does not have a supported file extension")) + (let ((file-type (denote-filetype-heuristics file))) + (unless (denote--file-has-front-matter-p file file-type) + (user-error "The file does not appear to have a front matter")) + (let* ((front-matter-template (denote--front-matter file-type)) + (components-in-template (denote--get-front-matter-components-order front-matter-template file-type)) + (title (if (memq 'title components-in-template) + (or (denote-retrieve-front-matter-title-value file file-type) "") + (or (denote-retrieve-filename-title file) ""))) + (keywords (if (memq 'keywords components-in-template) + (denote-retrieve-front-matter-keywords-value file file-type) + (denote-retrieve-filename-keywords-as-list file))) + (signature (if (memq 'signature components-in-template) + (or (denote-retrieve-front-matter-signature-value file file-type) "") + (or (denote-retrieve-filename-signature file) ""))) + ;; We need to use the identifier because the date line may + ;; not contain all the information. For example, + ;; "2024-01-01" does not have the time of the note. + (date (if (memq 'identifier components-in-template) + (when-let* ((id-value (denote-retrieve-front-matter-identifier-value file file-type))) + (denote-valid-date-p id-value)) + (denote-valid-date-p (or (denote-retrieve-filename-identifier file) ""))))) + (denote--rename-file file title keywords signature date) + (denote-update-dired-buffers)))) + +;;;###autoload +(defun denote-dired-rename-marked-files-using-front-matter () + "Call `denote-rename-file-using-front-matter' over the Dired marked files. +Refer to the documentation of that command for the technicalities. + +Marked files must count as notes for the purposes of Denote, which means +that they at least have an identifier in their file name and use a +supported file type, per the user option `denote-file-type'. Files that +do not meet this criterion are ignored because Denote cannot know if +they have front matter and what that may be." + (interactive nil dired-mode) + (if-let* ((marks (seq-filter + (lambda (m) + (and (file-regular-p m) + (denote-file-is-writable-and-supported-p m) + (denote-file-has-identifier-p m))) + (dired-get-marked-files)))) + (let ((denote--used-ids (denote--get-all-used-ids))) + (dolist (file marks) + (denote-rename-file-using-front-matter file)) + (denote-update-dired-buffers)) + (user-error "No marked Denote files; aborting"))) + +;;;;; Creation of front matter + +(make-obsolete 'denote-add-front-matter nil "Use `denote-rename-file' or related. Starting with version 4.0.0.") + +;;;###autoload +(defun denote-change-file-type-and-front-matter (file new-file-type) + "Change file type of FILE and add an appropriate front matter. + +If in Dired, consider FILE to be the one at point, else the +current file, else prompt with minibuffer completion for one. + +Add a front matter in the format of the NEW-FILE-TYPE at the +beginning of the file. + +Retrieve the title of FILE from a line starting with a title +field in its front matter, depending on the previous file +type (e.g. #+title for Org). The same process applies for +keywords. + +As a final step, ask for confirmation, showing the difference +between old and new file names. + +Important note: No attempt is made to modify any other elements +of the file. This needs to be done manually. + +Construct the file name in accordance with the user option +`denote-file-name-components-order'." + (interactive + (list + (denote--rename-dired-file-or-current-file-or-prompt) + (denote--valid-file-type (or (denote-file-type-prompt) denote-file-type)))) + (let* ((initial-state (if (find-buffer-visiting file) 'visited 'not-visited)) + (dir (file-name-directory file)) + (old-file-type (denote-filetype-heuristics file)) + (id (or (denote-retrieve-filename-identifier file) "")) + (date (if (string-empty-p id) nil (date-to-time id))) + (title (or (denote-retrieve-title-or-filename file old-file-type) "")) + (keywords (denote-retrieve-front-matter-keywords-value file old-file-type)) + (signature (or (denote-retrieve-filename-signature file) "")) + (new-extension (denote--file-extension new-file-type)) + (new-name (denote-format-file-name dir id keywords title new-extension signature)) + (max-mini-window-height denote-rename-max-mini-window-height)) + (when (denote-rename-file-prompt file new-name) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (when (and (denote-file-is-writable-and-supported-p new-name) + (denote-add-front-matter-prompt new-name)) + (denote-prepend-front-matter new-name title keywords signature date id new-file-type) + (denote--handle-save-and-kill-buffer 'rename new-name initial-state))))) + +;;;; The Denote faces + +(defgroup denote-faces () + "Faces for Denote." + :group 'denote) + +(defface denote-faces-link '((t :inherit link)) + "Face used to style Denote links in the buffer." + :group 'denote-faces + :package-version '(denote . "0.5.0")) + +(defface denote-faces-query-link '((t :inherit link-visited)) + "Face used to style Denote query links in the buffer." + :group 'denote-faces + :package-version '(denote . "4.0.0")) + +(defface denote-faces-subdirectory '((t :inherit bold)) + "Face for subdirectory of file name. +This should only ever needed in the backlinks' buffer (or +equivalent), not in Dired." + :group 'denote-faces + :package-version '(denote . "0.2.0")) + +(defface denote-faces-date '((t :inherit font-lock-variable-name-face)) + "Face for file name date in Dired buffers. +This is the part of the identifier that covers the year, month, +and day." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-time '((t :inherit denote-faces-date)) + "Face for file name time in Dired buffers. +This is the part of the identifier that covers the hours, minutes, +and seconds." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-title nil + "Face for file name title in Dired buffers." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-year '((t :inherit denote-faces-date)) + "Face for file name year in Dired buffers. +This is the part of the identifier that covers the year, month, and day." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-month '((t :inherit denote-faces-date)) + "Face for file name month in Dired buffers. +This is the part of the identifier that covers the year, month, and day." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-day '((t :inherit denote-faces-date)) + "Face for file name day in Dired buffers. +This is the part of the identifier that covers the year, month, and day." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-hour '((t :inherit denote-faces-date)) + "Face for file name hours in Dired buffers. +This is the part of the identifier that covers the hours, minutes, +and seconds." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-minute '((t :inherit denote-faces-date)) + "Face for file name minutes in Dired buffers. +This is the part of the identifier that covers the hours, minutes, +and seconds." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-second '((t :inherit denote-faces-date)) + "Face for file name seconds in Dired buffers. +This is the part of the identifier that covers the hours, minutes, +and seconds." + :group 'denote-faces + :package-version '(denote . "2.3.0")) + +(defface denote-faces-extension '((t :inherit shadow)) + "Face for file extension type in Dired buffers." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-keywords '((t :inherit font-lock-builtin-face)) + "Face for file name keywords in Dired buffers." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-signature '((t :inherit font-lock-warning-face)) + "Face for file name signature in Dired buffers." + :group 'denote-faces + :package-version '(denote . "2.0.0")) + +(defface denote-faces-delimiter + '((((class color) (min-colors 88) (background light)) + :foreground "gray70") + (((class color) (min-colors 88) (background dark)) + :foreground "gray30") + (t :inherit shadow)) + "Face for file name delimiters in Dired buffers." + :group 'denote-faces + :package-version '(denote . "0.1.0")) + +(defface denote-faces-time-delimiter '((t :inherit shadow)) + "Face for the delimiter between date and time in Dired buffers." + :group 'denote-faces + :package-version '(denote . "2.1.0")) + +;; The following matchers must obey the doc of `font-lock-keywords': +;; - Have one parameter, the limit of the search +;; - Set match-data (and restore it on failure) +;; - Move point after the match (or restore it on failure). +;; - Return t on success and nil on failure. re-search-forward returns (point) on success. It may be better to do the same. + +(defun denote-faces-dired-file-name-matcher (limit) + "Find the file name in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point)) + (line-found nil)) + ;; Find the next non empty line that contains a Dired file name + (while (and (not line-found) + (re-search-forward "^.+$" limit t)) + ;; dired-move-to-filename moves the point even if it returns nil + (let ((saved-point (point))) + (if (and (dired-move-to-filename) + (save-match-data + (denote-file-has-denoted-filename-p (buffer-substring (point) (line-end-position))))) + (setq line-found t) + (goto-char saved-point)))) + (if line-found + (let ((beginning-point (point))) + (goto-char (match-end 0)) + (set-match-data (list beginning-point (match-end 0))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defun denote-faces-directory-matcher (limit) + "Match the directory in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point))) + (if (re-search-forward "\\(?1:.*/\\)[^/]*$" limit t) + (progn + (goto-char (match-end 1)) + (set-match-data (list (match-beginning 1) (match-end 1))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defun denote-faces-signature-matcher (limit) + "Match the signature in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point))) + (if (or (re-search-forward "==\\(?1:[^/]*?\\)\\(@@\\|--\\|__\\|==\\|\\.\\)[^/]*$" limit t) + (re-search-forward "==\\(?1:[^/]*\\)$" limit t)) + (progn + (goto-char (match-end 1)) + (set-match-data (list (match-beginning 1) (match-end 1))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defun denote-faces-identifier-matcher (limit) + "Match a general identifier in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point))) + (if (or (re-search-forward "@@\\(?1:[^/]*?\\)\\(@@\\|--\\|__\\|==\\|\\.\\)[^/]*$" limit t) + (re-search-forward "@@\\(?1:[^/]*\\)$" limit t)) + (progn + (goto-char (match-end 1)) + (set-match-data (list (match-beginning 1) (match-end 1))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defun denote-faces-title-matcher (limit) + "Match the title in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point))) + (if (or (re-search-forward "--\\(?1:[^/]*?\\)\\(@@\\|__\\|==\\|\\.\\)[^/]*$" limit t) + (re-search-forward "--\\(?1:[^/]*\\)$" limit t)) + (progn + (goto-char (match-end 1)) + (set-match-data (list (match-beginning 1) (match-end 1))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defun denote-faces-keywords-matcher (limit) + "Match the keywords in a Dired line, not looking beyond LIMIT." + (let ((initial-match-data (match-data)) + (initial-point (point))) + (if (or (re-search-forward "__\\(?1:[^/]*?\\)\\(@@\\|--\\|__\\|==\\|\\.\\)[^/]*$" limit t) + (re-search-forward "__\\(?1:[^/]*\\)$" limit t)) + (progn + (goto-char (match-end 1)) + (set-match-data (list (match-beginning 1) (match-end 1))) + (point)) + (goto-char initial-point) + (set-match-data initial-match-data) + nil))) + +(defconst denote-faces-matchers + `((denote-faces-directory-matcher + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-subdirectory nil t)) + ;; Identifier with format 00000000T000000 + ("\\(?1:[0-9]\\{4\\}\\)\\(?2:[0-9]\\{2\\}\\)\\(?3:[0-9]\\{2\\}\\)\\(?7:T\\)\\(?4:[0-9]\\{2\\}\\)\\(?5:[0-9]\\{2\\}\\)\\(?6:[0-9]\\{2\\}\\)" + (goto-char (match-beginning 0)) ; pre-form, executed before looking for the first identifier + (goto-char (match-end 0)) ; post-form, executed after all matches (identifiers here) are found + (1 'denote-faces-year nil t) + (2 'denote-faces-month nil t) + (3 'denote-faces-day nil t) + (4 'denote-faces-hour nil t) + (5 'denote-faces-minute nil t) + (6 'denote-faces-second nil t) + (7 'denote-faces-delimiter nil t)) + ;; Identifier with general format (not yet possible) + (denote-faces-identifier-matcher + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-date nil t)) + ;; Title + (denote-faces-title-matcher + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-title nil t)) + ;; Keywords + (denote-faces-keywords-matcher + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-keywords nil t)) + ;; Signature + (denote-faces-signature-matcher + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-signature nil t)) + ;; Delimiters + ("\\(@@\\|--\\|__\\|==\\)" + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-delimiter nil t)) + ;; Extension + ("\\..*$" + (goto-char (match-beginning 0)) + (goto-char (match-end 0)) + (0 'denote-faces-extension nil t))) + "Matchers for fontification of file names.") + +(defconst denote-faces-file-name-keywords-for-dired + `((denote-faces-dired-file-name-matcher ,@denote-faces-matchers)) + "Keywords for fontification of file names.") + +(make-obsolete-variable 'denote-faces-file-name-keywords-for-backlinks nil "4.0.0") + +(defface denote-faces-prompt-old-name '((t :inherit error)) + "Face for the old name shown in the prompt of `denote-rename-file' etc." + :group 'denote-faces + :package-version '(denote . "2.2.0")) + +(defface denote-faces-prompt-new-name '((t :inherit success)) + "Face for the new name shown in the prompt of `denote-rename-file' etc." + :group 'denote-faces + :package-version '(denote . "2.2.0")) + +(defface denote-faces-prompt-current-name '((t :inherit denote-faces-prompt-old-name)) + "Face for the current file shown in the prompt of `denote-rename-file' etc." + :group 'denote-faces + :package-version '(denote . "2.2.0")) + +;;;; Fontification in Dired + +(defgroup denote-dired () + "Integration between Denote and Dired." + :group 'denote) + +(defcustom denote-dired-directories (list denote-directory) + "List of directories where `denote-dired-mode' should apply to. +For this to take effect, add `denote-dired-mode-in-directories', +to the `dired-mode-hook'. + +If `denote-dired-directories-include-subdirectories' is non-nil, +also apply the effect to all subdirectories of those specified in +the list." + :type '(repeat directory) + :package-version '(denote . "0.1.0") + :link '(info-link "(denote) Fontification in Dired") + :group 'denote-dired) + +(defcustom denote-dired-directories-include-subdirectories nil + "If non-nil `denote-dired-directories' also affects all subdirectories. +Otherwise `denote-dired-directories' works only with exact matches." + :package-version '(denote . "2.2.0") + :link '(info-link "(denote) Fontification in Dired") + :type 'boolean + :group 'denote-dired) + +;; FIXME 2022-08-12: Make `denote-dired-mode' work with diredfl. This +;; may prove challenging. + +(defun denote-dired-add-font-lock (&rest _) + "Append `denote-faces-file-name-keywords' to font lock keywords." + ;; NOTE 2023-10-28: I tried to add the first argument and then + ;; experimented with various combinations of keywords, such as + ;; `(,@dired-font-lock-keywords ,@denote-faces-file-name-keywords). + ;; None of them could be unset upon disabling `denote-dired-mode'. + ;; As such, I am using the `when' here. + (when (derived-mode-p 'dired-mode) + (font-lock-add-keywords nil denote-faces-file-name-keywords-for-dired t))) + +(defun denote-dired-remove-font-lock (&rest _) + "Remove `denote-faces-file-name-keywords' from font lock keywords." + ;; See NOTE in `denote-dired-add-font-lock'. + (when (derived-mode-p 'dired-mode) + (font-lock-remove-keywords nil denote-faces-file-name-keywords-for-dired))) + +(declare-function wdired-change-to-wdired-mode "wdired") +(declare-function wdired-finish-edit "wdired") + +;;;###autoload +(define-minor-mode denote-dired-mode + "Fontify all Denote-style file names. +Add this or `denote-dired-mode-in-directories' to +`dired-mode-hook'." + :global nil + :group 'denote-dired + (if denote-dired-mode + (progn + (denote-dired-add-font-lock) + (advice-add #'wdired-change-to-wdired-mode :after #'denote-dired-add-font-lock) + (advice-add #'wdired-finish-edit :after #'denote-dired-add-font-lock)) + (denote-dired-remove-font-lock) + (advice-remove #'wdired-change-to-wdired-mode #'denote-dired-add-font-lock) + (advice-remove #'wdired-finish-edit #'denote-dired-add-font-lock)) + (font-lock-flush (point-min) (point-max))) + +(defun denote-dired--modes-dirs-as-dirs () + "Return `denote-dired-directories' as directories. +The intent is to basically make sure that however a path is +written, it is always returned as a directory." + (mapcar + (lambda (dir) + (file-name-as-directory (file-truename dir))) + denote-dired-directories)) + +;;;###autoload +(defun denote-dired-mode-in-directories () + "Enable `denote-dired-mode' in `denote-dired-directories'. +Add this function to `dired-mode-hook'. + +If `denote-dired-directories-include-subdirectories' is non-nil, +also enable it in all subdirectories." + (when-let* ((dirs (denote-dired--modes-dirs-as-dirs)) + ;; Also include subdirs + ((or (member (file-truename default-directory) dirs) + (and denote-dired-directories-include-subdirectories + (seq-some + (lambda (dir) + (string-prefix-p dir (file-truename default-directory))) + dirs))))) + (denote-dired-mode 1))) + +;;;; The linking facility + +;;;;; Link to note + +(defvar denote-org-link-format "[[denote:%s][%s]]" + "Format of Org link to note. +The value is passed to `format' with IDENTIFIER and TITLE +arguments, in this order. + +Also see `denote-org-link-in-context-regexp'.") + +(defvar denote-md-link-format "[%2$s](denote:%1$s)" + "Format of Markdown link to note. +The %N$s notation used in the default value is for `format' as +the supplied arguments are IDENTIFIER and TITLE, in this order. + +Also see `denote-md-link-in-context-regexp'.") + +(defvar denote-id-only-link-format "[[denote:%s]]" + "Format of identifier-only link to note. +The value is passed to `format' with IDENTIFIER as its sole +argument. + +Also see `denote-id-only-link-in-context-regexp'.") + +(defvar denote-org-link-in-context-regexp + (concat "\\[\\[" "denote:" + "\\(?1:[^][]*?\\)" + "\\(?:::.*\\)?" "]" + "\\[" "\\(?2:" ".*?" "\\)" "]]") + "Regexp to match an Org link in its context. +The format of such links is `denote-org-link-format'.") + +(defvar denote-md-link-in-context-regexp + (concat "\\[" "\\(?2:" ".*?" "\\)" "]" + "(denote:" "\\(?1:[^][]*?\\)" ")") + "Regexp to match a Markdown link in its context. +The format of such links is `denote-md-link-format'.") + +(defvar denote-id-only-link-in-context-regexp + (concat "\\[\\[" "denote:" "\\(?1:[^][]*?\\)" "]]") + "Regexp to match an identifier-only link in its context. +The format of such links is `denote-id-only-link-format'." ) + +(defun denote-format-link (file description file-type id-only &optional include-date) + "Prepare link to FILE using DESCRIPTION. + +FILE-TYPE and ID-ONLY are used to get the format of the link. +See the `:link' property of `denote-file-types'. + +With optional INCLUDE-DATE, convert the identifier using +`denote--id-to-date' and append it to DESCRIPTION." + (let* ((identifier (denote-retrieve-filename-identifier file)) + (desc (if include-date + (format "%s (%s)" description (denote--id-to-date identifier)) + description))) + (format + (cond + ((or id-only (null description) (string-empty-p description)) + denote-id-only-link-format) + ;; NOTE 2024-05-20: If there is no file type, we want to use the + ;; Org format because it is still a usable link with the help of + ;; the command `org-open-at-point-global'. + ((null file-type) + (denote--link-format 'org)) + (t + (denote--link-format file-type))) + identifier + desc))) + +(defun denote-link-description-with-signature-and-title (file) + "Return link description for FILE. + +- If the region is active, use it as the description. + +- If FILE has a signature, then format the description as a sequence of + the signature text and the title with two spaces between them. + +- If FILE does not have a signature, then use its title as the + description. + +This is useful as the value of the user option +`denote-link-description-function'." + (let* ((file-type (denote-filetype-heuristics file)) + (signature (denote-retrieve-filename-signature file)) + (title (denote-retrieve-title-or-filename file file-type)) + (region-text (denote--get-active-region-content))) + (cond + (region-text region-text) + ((and signature title) (format "%s %s" signature title)) + (title (format "%s" title)) + (signature (format "%s" signature)) + (t "")))) + +(defun denote--get-active-region-content () + "Return the text of the active region, else nil." + (when-let* (((region-active-p)) + (beg (region-beginning)) + (end (region-end))) + (string-trim (buffer-substring-no-properties beg end)))) + +(defun denote--delete-active-region-content () + "Delete the content of the active region, if any." + (when-let* (((region-active-p)) + (beg (region-beginning)) + (end (region-end))) + (delete-region beg end))) + +(defun denote-get-link-description (file) + "Return a link description for FILE. + +If `denote-link-description-format' is a function, call it with FILE as +an argument. The function should return a string, representing the link +description. + +If the user option `denote-link-description-format' is a string, parse +it to substitute any format specifiers therein with their respective +values (see the documentation of that user option). If the region is +active, use it as the description." + (cond + ((functionp denote-link-description-format) + (funcall denote-link-description-format file)) + ((stringp denote-link-description-format) + (if-let* ((region (denote--get-active-region-content))) + region + (let ((type (denote-filetype-heuristics file))) + (string-trim + (format-spec denote-link-description-format + (list (cons ?t (cond + ((denote-retrieve-front-matter-title-value file (denote-filetype-heuristics file))) + ((denote-retrieve-filename-title file)) + (t ""))) + (cons ?T (or (denote-retrieve-filename-title file) "")) + (cons ?i (or (denote-retrieve-filename-identifier file) "")) + ;; TODO 2025-04-03: Maybe we can have something like `denote-date-format' here, + ;; but I think we are okay with a hardcoded value. + (cons ?I (or (when-let* ((id (denote-retrieve-filename-identifier file)) + (_ (denote-valid-date-p id))) + (format-time-string "%A, %e %B %Y" (date-to-time (denote--id-to-date id)))) + "")) + (cons ?D (cond + ((denote-retrieve-front-matter-title-value file type)) + ((denote-retrieve-filename-title file)) + ((when-let* ((id (denote-retrieve-filename-identifier file))) + (if (denote-valid-date-p id) + (format-time-string "%A, %e %B %Y" (date-to-time (denote--id-to-date id))) + id))) + (t ""))) + (cons ?d (or (denote-retrieve-filename-identifier file) "")) + (cons ?s (or (denote-retrieve-filename-signature file) "")) + (cons ?k (or (denote-retrieve-filename-keywords file) "")) + (cons ?% "%")) + 'delete))))) + (t + (error "The `denote-link-description-format' must be a function or string")))) + +(define-obsolete-function-alias + 'denote--link-get-description + 'denote-get-link-description + "4.0.0") + +;;;###autoload +(defun denote-link (file file-type description &optional id-only) + "Create link to FILE note in variable `denote-directory' with DESCRIPTION. + +When called interactively, prompt for FILE using completion. In this +case, derive FILE-TYPE from the current buffer. FILE-TYPE is used to +determine the format of the link. + +Return the DESCRIPTION of the link in the format specified by +`denote-link-description-format'. The default is to return the text of +the active region or the title of the note (plus the signature if +present). + +With optional ID-ONLY as a non-nil argument, such as with a universal +prefix (\\[universal-argument]), insert links with just the identifier +and no further description. In this case, the link format is always +[[denote:IDENTIFIER]]. + +If the DESCRIPTION is empty, format the link the same as with ID-ONLY. + +When called from Lisp, FILE is a string representing a full file system +path. FILE-TYPE is a symbol as described in the user option +`denote-file-type'. DESCRIPTION is a string. Whether the caller treats +the active region specially, is up to it." + (interactive + (let* ((file (denote-file-prompt nil "Link to FILE")) + (file-type (denote-filetype-heuristics buffer-file-name)) + (description (when (file-exists-p file) + (denote-get-link-description file)))) + (list file file-type description current-prefix-arg))) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (unless (file-exists-p file) + (user-error "The linked file does not exist")) + (denote--delete-active-region-content) + (insert (denote-format-link file description file-type id-only))) + +(defalias 'denote-insert-link 'denote-link + "Alias for `denote-link' command.") + +(make-obsolete 'denote-link-with-signature nil " 4.0.0: Use the `denote-link-description-format'.") + +(defun denote-link--collect-identifiers (regexp) + "Return collection of identifiers in buffer matching REGEXP." + (let (matches) + (save-excursion + (goto-char (point-min)) + (while (or (re-search-forward regexp nil t) + (re-search-forward denote-id-only-link-in-context-regexp nil t)) + (push (match-string-no-properties 1) matches))) + matches)) + +(defun denote-link--expand-identifiers (regexp) + "Expend identifiers matching REGEXP into file paths." + (let ((files (denote-directory-files)) + found-files) + (dolist (file files) + (dolist (i (denote-link--collect-identifiers regexp)) + (when (string= i (denote-retrieve-filename-identifier file)) + (push file found-files)))) + found-files)) + +(defvar denote-link-find-file-history nil + "History for `denote-find-link'.") + +(defalias 'denote-link--find-file-history 'denote-link-find-file-history + "Compatibility alias for `denote-link-find-file-history'.") + +(defun denote-select-linked-file-prompt (files) + "Prompt for linked file among FILES." + (let ((file-names (mapcar #'denote-get-file-name-relative-to-denote-directory files))) + (completing-read + "Find linked file: " + (denote--completion-table 'file file-names) + nil t nil 'denote-link-find-file-history))) + +(define-obsolete-function-alias + 'denote-link--find-file-prompt + 'denote-select-linked-file-prompt + "3.0.0") + +(defun denote-link-return-links (&optional file) + "Return list of links in current or optional FILE. +Also see `denote-link-return-backlinks'." + (when-let* ((current-file (or file (buffer-file-name))) + ((denote-file-has-supported-extension-p current-file)) + (file-type (denote-filetype-heuristics current-file)) + (regexp (denote--link-in-context-regexp file-type)) + (files (denote-directory-files)) + (file-identifiers + (with-temp-buffer + (insert-file-contents current-file) + (denote-link--collect-identifiers regexp))) + (file-identifiers-hash-table (make-hash-table :test 'equal))) + (dolist (id file-identifiers) + (puthash id t file-identifiers-hash-table)) + (let ((found-files)) + (dolist (file files) + (when (gethash (denote-retrieve-filename-identifier file) file-identifiers-hash-table) + (push file found-files))) + found-files))) + +(defalias 'denote-link-return-forelinks 'denote-link-return-links + "Alias for `denote-link-return-links'.") + +;;;###autoload +(defun denote-find-link () + "Use minibuffer completion to visit linked file. +Also see `denote-find-backlink'." + (declare (interactive-only t)) + (interactive) + (find-file + (concat + (denote-directory) + (denote-select-linked-file-prompt + (or (denote-link-return-links) + (user-error "No links found")))))) + +;;;###autoload +(defun denote-link-after-creating (&optional id-only) + "Create new note in the background and link to it directly. + +Use `denote' interactively to produce the new note. Its doc +string explains which prompts will be used and under what +conditions. + +With optional ID-ONLY as a prefix argument create a link that +consists of just the identifier. Else try to also include the +file's title. This has the same meaning as in `denote-link'. + +For a variant of this, see `denote-link-after-creating-with-command'. + +IMPORTANT NOTE: Normally, `denote' does not save the buffer it +produces for the new note. This is a safety precaution to not +write to disk unless the user wants it (e.g. the user may choose +to kill the buffer, thus cancelling the creation of the note). +However, for this command the creation of the note happens in the +background and the user may miss the step of saving their buffer. +We thus have to save the buffer in order to (i) establish valid +links, and (ii) retrieve whatever front matter from the target +file. Though see `denote-save-buffer-after-creation'." + (interactive "P") + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (let* ((type (denote-filetype-heuristics (buffer-file-name))) + (path (denote--command-with-features #'denote nil nil :save :in-background)) + (description (denote-get-link-description path))) + (denote-link path type description id-only))) + +;;;###autoload +(defun denote-link-after-creating-with-command (command &optional id-only) + "Like `denote-link-after-creating' but prompt for note-making COMMAND. +Use this to, for example, call `denote-signature' so that the +newly created note has a signature as part of its file name. + +Optional ID-ONLY has the same meaning as in the command +`denote-link-after-creating'." + (interactive + (list + (denote-command-prompt) + current-prefix-arg)) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (let* ((type (denote-filetype-heuristics (buffer-file-name))) + (path (denote--command-with-features command nil nil :save :in-background)) + (description (denote-get-link-description path))) + (denote-link path type description id-only))) + +;;;###autoload +(defun denote-link-or-create (target &optional id-only) + "Use `denote-link' on TARGET file, creating it if necessary. + +If TARGET file does not exist, call `denote-link-after-creating' which +runs the `denote' command interactively to create the file. The +established link will then be targeting that new file. In that case, +use the last input at the file prompt as the default value of the title +prompt. + +With optional ID-ONLY as a prefix argument create a link that +consists of just the identifier. Else try to also include the +file's title. This has the same meaning as in `denote-link'." + (interactive + (let* ((target (denote-file-prompt nil "Select file (RET on no match to create it)" :no-require-match))) + (unless (and target (file-exists-p target)) + (setq target (denote--command-with-features #'denote :use-file-prompt-as-def-title :ignore-region :save :in-background))) + (list target current-prefix-arg))) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (denote-link target + (denote-filetype-heuristics (buffer-file-name)) + (denote-get-link-description target) + id-only)) + +(defalias 'denote-link-to-existing-or-new-note 'denote-link-or-create + "Alias for `denote-link-or-create' command.") + +;;;;; Links' buffer (query links and backlinks using `denote-query-mode') + +(define-obsolete-function-alias + 'denote-backlinks-mode + 'denote-query-mode + "4.0.0") + +(declare-function outline-cycle "outline" (&optional event)) +(declare-function outline-cycle-buffer "outline" (&optional level)) +(declare-function outline-next-heading "outline" ()) +(declare-function outline-previous-heading "outline" ()) + +(defvar denote-query-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "a" #'outline-cycle-buffer) + (define-key map "f" #'denote-query-focus-last-search) + (define-key map "k" #'outline-previous-heading) + (define-key map "j" #'outline-next-heading) + (define-key map "o" #'delete-other-windows) + (define-key map "s" #'denote-grep) + (define-key map "v" #'outline-cycle) + (define-key map "x" #'denote-query-exclude-files) + (define-key map "i" #'denote-query-only-include-files) + (define-key map "l" #'recenter-current-error) + (define-key map "X" #'denote-query-exclude-files-with-keywords) + (define-key map "I" #'denote-query-only-include-files-with-keywords) + (define-key map "G" #'denote-query-clear-all-filters) + map) + "Keymap for `denote-query-mode' buffers.") + +(define-derived-mode denote-query-mode xref--xref-buffer-mode "Denote Query" + "Major mode for queries found in the variable `denote-directory'. +This is used by the commands `denote-backlinks', `denote-grep', +`denote-query-contents-link', among others." + :interactive nil + (setq-local outline-minor-mode-use-buttons 'in-margins) + (outline-minor-mode 1)) + +(make-obsolete 'denote-link--backlink-find-file nil "4.0.0") +(make-obsolete 'denote-link--display-buffer nil "4.0.0") +(make-obsolete 'denote-backlinks-mode-next nil "4.0.0") +(make-obsolete 'denote-backlinks-mode-previous nil "4.0.0") +(make-obsolete 'denote-backlinks-toggle-context nil "4.0.0") +(make-obsolete-variable 'denote-backlinks-mode-map nil "4.0.0") + +(define-obsolete-function-alias + 'denote-link--prepare-backlinks + 'denote-make-links-buffer + "4.0.0") + +(make-obsolete-variable 'denote-backlinks-show-context nil "4.0.0") + +(define-obsolete-variable-alias + 'denote-link-backlinks-display-buffer-action + 'denote-backlinks-display-buffer-action + "3.1.0") + +(defgroup denote-query () + "Integration between Denote and Xref for grep/query/backlink buffers." + :group 'denote) + +(defcustom denote-backlinks-display-buffer-action + '((display-buffer-reuse-mode-window display-buffer-below-selected) + (mode . denote-query-mode) + (window-height . fit-window-to-buffer)) + "The action used to display the current file's backlinks buffer. + +The value has the form (FUNCTION . ALIST), where FUNCTION is +either an \"action function\", a list thereof, or possibly an +empty list. ALIST is a list of \"action alist\" which may be +omitted (or be empty). + +Sample configuration to display the buffer in a side window on +the left of the Emacs frame: + + (setq denote-backlinks-display-buffer-action + (quote ((display-buffer-reuse-window display-buffer-in-side-window) + (side . left) + (slot . 99) + (window-width . 0.3) + (dedicated . t) + (preserve-size . (t . t))))) + +See Info node `(elisp) Displaying Buffers' for more details +and/or the documentation string of `display-buffer'." + :risky t + :type `(choice + (alist :key-type + (choice :tag "Condition" + regexp + (function :tag "Matcher function")) + :value-type ,display-buffer--action-custom-type) + (function :tag "Custom function to return an action alist")) + :package-version '(denote . "3.1.0") + :group 'denote-query) + +(defcustom denote-query-links-display-buffer-action + '((display-buffer-reuse-mode-window display-buffer-below-selected) + (mode . (denote-query-mode dired)) + (window-height . 0.3) + (preserve-size . (t . t))) + "The action used to display query links. +This is the same as `denote-backlinks-display-buffer-action'. Refer to +its documentation for the technicalities." + :risky t + :type `(choice + (alist :key-type + (choice :tag "Condition" + regexp + (function :tag "Matcher function")) + :value-type ,display-buffer--action-custom-type) + (function :tag "Custom function to return an action alist")) + :package-version '(denote . "4.0.0") + :group 'denote-query) + +(defcustom denote-query-format-heading-function #'identity + "Function used to construct headings for files matched by a query. + +It is called with a single argument, the path to the note file, and it +should always return a string." + :package-version '(denote . "4.0.0") + :link '(info-link "(denote) Use denote-grep to search inside files") + :group 'denote-query + :type 'function) + +(defcustom denote-query-untitled-string "[Untitled]" + "String to use as heading for untitled notes in links' buffer. + +Used only by `denote-query-extract-title'." + :package-version '(denote . "4.0.0") + :link '(info-link "(denote) Use denote-grep to search inside files") + :group 'denote-query + :type 'string) + +(defun denote-query-extract-title (file) + "Extract note title from FILE front matter. + +When no title is found, return title found in FILE name. + +When that doesn't work, return `denote-grep-untitled-string'. + +Intended to be used as `denote-query-format-heading-function'." + (if-let* ((type (denote-filetype-heuristics file)) + (title (denote-retrieve-title-or-filename file type)) + (_ (not (string-blank-p title)))) + title + denote-query-untitled-string)) + +;; NOTE 2025-03-24: The `&rest' is there because we used to have an +;; extra SHOW-CONTEXT parameter. This way we do not break anybody's +;; code, even if we slightly modify the behaviour. +(defun denote-make-links-buffer (query &optional files buffer-name display-buffer-action &rest _) + "Create links' buffer called BUFFER-NAME for QUERY. + +Optional FILES can be a list of files to search for. It can also be a +regexp, which limits the files accordingly per `denote-directory-files'. + +Optional DISPLAY-BUFFER-ACTION is a `display-buffer' action and +concomitant alist, such as `denote-backlinks-display-buffer-action'." + (let* ((inhibit-read-only t) + (file buffer-file-name) + (buffer (or buffer-name (format-message "Denote query for `%s'" query))) + ;; We retrieve results in absolute form and change the + ;; absolute path to a relative path below. We could add a + ;; suitable function and the results would be automatically + ;; in relative form, but eventually notes may not be all + ;; under a common directory (or project). + (xref-alist (denote-retrieve-xref-alist query files)) + (dir (denote-directory))) + (unless xref-alist + (error "No matches for query `%s'" query)) + ;; Update internal variables + (setq denote-query--last-files nil) + (setq denote-query--last-query query) + (dolist (x xref-alist) + (let* ((file-xref (car x)) + (file + ;; NOTE: Unfortunately, the car of the xref construct is + ;; not reliable; sometimes it's absolute, sometimes it + ;; is not + (if (file-name-absolute-p file-xref) + file-xref + (xref-location-group + (xref-match-item-location (car (last x))))))) + ;; Add to current set of files + (push file denote-query--last-files) + ;; Format heading + (setf (car x) (funcall denote-query-format-heading-function file)))) + (delete-dups denote-query--last-files) + ;; Insert results + (with-current-buffer (get-buffer-create buffer) + (erase-buffer) + (denote-query-mode) + ;; In the links' buffer, the values of variables set in a + ;; `.dir-locals.el` do not apply. We need to set + ;; `denote-directory' here because the buttons depend on it. + ;; Moreover, its value is overwritten after enabling the major + ;; mode, so it needs to be set after. + (setq-local denote-directory dir) + (setq overlay-arrow-position nil) + (goto-char (point-min)) + (xref--insert-xrefs xref-alist) + (goto-char (point-min)) + (setq-local revert-buffer-function + (lambda (_ignore-auto _noconfirm) + (when-let* ((buffer-file-name file)) + (denote-make-links-buffer query files buffer-name display-buffer-action))))) + (display-buffer buffer display-buffer-action))) + +(defvar denote-query-links-buffer-function #'denote-make-links-buffer + "Function to make an Xref buffer showing query link results. +It accepts the same arguments as `denote-make-links-buffer'.") + +(defun denote-query-focus-last-search (query) + "Search QUERY in the content of files which matched the last search. +\"Last search\" here means any call to `denote-grep', +`denote-backlinks', `denote-query-contents-link', or, generally, any +command that relies on the `denote-make-links-buffer'." + (interactive (list (denote-grep-query-prompt :focused)) denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (denote-make-links-buffer + query denote-query--last-files + nil '(display-buffer-same-window)) + (message "Searching `%s' in files matched previously" query)) + +(defun denote-query-exclude-files (regexp) + "Exclude files whose name matches REGEXP from current search buffer. + +This is useful even if you don't know regular expressions, given the +Denote file-naming scheme. For instance, to exclude notes with the +keyword \"philosophy\" from current search buffer, type +‘\\\\[denote-query-exclude-files] _philosophy +RET’. + +Internally, this works by generating a new call to +`denote-make-links-buffer' with the same QUERY as the last one, but with +a set of files gotten from checking REGEXP against last matched files. + +When called from Lisp, REGEXP can be a list; in that case, it should be +a list of fixed strings (NOT regexps) to check against last matched +files. Files that match any of the strings get excluded. Internally, +the list is processed using `regexp-opt'. For an example of this usage, +see `denote-query-exclude-files-with-keywords'." + (interactive (list (denote-grep-file-regexp-prompt)) denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (let (final-files) + (dolist (file denote-query--last-files) + (unless (string-match + ;; Support list of strings as REGEXP + (if (listp regexp) + (regexp-opt regexp) + regexp) + file) + (push file final-files))) + (if final-files + (denote-make-links-buffer denote-query--last-query final-files) + (user-error "No remaining files when applying that filter")) + (message "Excluding files matching `%s'" regexp))) + +(defun denote-query-only-include-files (regexp) + "Exclude file names not matching REGEXP from current query buffer. + +See `denote-query-exclude-files' for details, including the behaviour +when REGEXP is a list." + (interactive (list (denote-grep-file-regexp-prompt :include)) denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (let (final-files) + (dolist (file denote-query--last-files) + (when (string-match + ;; Support list of strings as REGEXP + (if (listp regexp) + (regexp-opt regexp) + regexp) + file) + (push file final-files))) + (if final-files + (denote-make-links-buffer denote-query--last-query final-files) + (user-error "No remaining files when applying that filter")) + (message "Only including files matching `%s'" regexp))) + +(defun denote-query-exclude-files-with-keywords (keywords) + "Exclude files with KEYWORDS from current query buffer. + +KEYWORDS should be a list of keywords (without underscore). + +Interactively, KEYWORDS are read from the minibuffer using +`completing-read-multiple', which see." + (interactive + (list (denote-keywords-prompt "Exclude files with keywords")) + denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (denote-query-exclude-files + (mapcar (lambda (kw) (concat "_" kw)) keywords))) + +(defun denote-query-only-include-files-with-keywords (keywords) + "Exclude files without KEYWORDS from current query buffer. + +See `denote-query-exclude-files-with-keywords' for details." + (interactive + (list (denote-keywords-prompt "Only include files with keywords")) + denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (denote-query-only-include-files + (mapcar (lambda (kw) (concat "_" kw)) keywords))) + +(defun denote-query-clear-all-filters () + "Run last search with the full set of files in the variable `denote-directory'. + +This effectively gets ride of any interactive filter applied (by the +means of e.g. `denote-query-exclude-files')." + (interactive nil denote-query-mode) + (unless (derived-mode-p 'denote-query-mode) + (user-error "Only use this command inside the `denote-query-mode'")) + (denote-make-links-buffer denote-query--last-query) + (message "Cleared all filters")) + +;;;;;; Additional features for searching file contents + +(defvar denote-grep-history nil + "Minibuffer history of content searches performed by `denote-grep'. +Also see `denote-grep-file-regexp-history'.") + +(defcustom denote-grep-display-buffer-action + '((display-buffer-same-window) + (mode . denote-query-mode)) + "The action used to display search results from `denote-grep'. +This is the same as `denote-backlinks-display-buffer-action'. Refer to +its documentation for the technicalities." + :risky t + :type `(choice + (alist :key-type + (choice :tag "Condition" + regexp + (function :tag "Matcher function")) + :value-type ,display-buffer--action-custom-type) + (function :tag "Custom function to return an action alist")) + :package-version '(denote . "4.0.0") + :group 'denote-query) + +(defun denote-grep-query-prompt (&optional type) + "Prompt for a grep query in the minibuffer. + +The prompt assumes a search in all files, unless TYPE is non-nil. + +TYPE can be one of :focused (for a focused search (a search among +matching files), see `denote-query-focus-last-search'), :dired (for a +search in marked Dired files, see `denote-grep-marked-dired-files') or +:region (for a search in files referenced in region, see +`denote-grep-files-referenced-in-region'). + +TYPE only affects the prompt, not the returned value." + (read-string + (cond ((eq type :focused) + "Search (only files matched last): ") + ((eq type :dired) + "Search (only marked dired files): ") + ((eq type :region) + "Search (only files referenced in region): ") + (t "Search (all Denote files): ")) + nil 'denote-grep-history)) + +(defvar denote-grep-file-regexp-history nil + "Minibuffer history for `denote-grep' commands asking for a file regexp. +Also see `denote-grep-history'.") + +(defun denote-grep-file-regexp-prompt (&optional include) + "Prompt for a file regexp in the minibuffer. + +The prompt assumes the user wants to exclude files, unless INCLUDE is +non-nil." + (read-string + (if (not include) + "Exclude file names matching: " + "Only include file names matching: ") + nil 'denote-grep-file-regexp-history)) + +;;;###autoload +(defun denote-grep (query) + "Search QUERY in the content of Denote files. +QUERY should be a regular expression accepted by `xref-search-program'. + +The files to search for are those returned by `denote-directory-files' +with a non-nil TEXT-ONLY argument. + +Results are put in a buffer which allows folding and further +filtering (see the manual for details). + +You can insert a link to a grep search in any note by using the command +`denote-query-contents-link'." + (interactive (list (denote-grep-query-prompt))) + (let (denote-query--omit-current) + (denote-make-links-buffer query nil nil denote-grep-display-buffer-action))) + +;;;###autoload +(defun denote-grep-marked-dired-files (query) + "Do the equivalent of `denote-grep' for QUERY in marked Dired files." + (interactive (list (denote-grep-query-prompt :dired))) + (if-let* ((files (dired-get-marked-files))) + (denote-make-links-buffer query files nil denote-grep-display-buffer-action) + (user-error "No marked files"))) + +(defun denote-grep--get-files-referenced-in-region (start end) + "Return a list with all Denote files referenced between START and END. +START and END are buffer positions, as integers. A reference to a file +is the mere presence of its identifier. + +Return a list with the absoulte path of referenced files." + (let (id-list) + (save-excursion + (save-restriction + (narrow-to-region start end) + (goto-char (point-min)) + (while (re-search-forward denote-id-regexp nil t) + (push (denote-get-path-by-id (match-string 0)) id-list)))) + id-list)) + +;;;###autoload +(defun denote-grep-files-referenced-in-region (query start end) + "Perform `denote-grep' QUERY in files referenced between START and END. +When called interactively, prompt for QUERY. Also get START and END as +the buffer positions that delimit the marked region. When called from +Lisp, QUERY is a string, while START and END are buffer positions, as +integers. + +Find references to files by their identifier. This includes links with +just the identifier (as described in `denote-link' and related), links +written by an Org dynamic block (see the `denote-org' package), or even +file listings such as those of `dired' and the command-line `ls' program." + (interactive + (if (region-active-p) + (list + (denote-grep-query-prompt :region) + (region-beginning) + (region-end)) + (user-error "No region is active; aborting"))) + (if-let* ((files (denote-grep--get-files-referenced-in-region start end))) + (denote-make-links-buffer query files nil denote-grep-display-buffer-action) + (user-error "No files referenced in region"))) + +;;;;;; Backlinks + +(defun denote--backlinks-get-buffer-name (file id) + "Format a buffer name for `denote-backlinks'. +Use FILE to detect a suitable title with which to name the buffer. Else +use the ID." + (if-let* ((type (denote-filetype-heuristics file)) + (title (denote-retrieve-front-matter-title-value file type))) + (format "*Denote FILE backlinks for %S*" title) + (format "*Denote FILE backlinks for %s*" id))) + +;;;###autoload +(defun denote-backlinks () + "Produce a buffer with backlinks to the current note. + +Show the names of files linking to the current file. Include the +context of each link if the user option `denote-backlinks-show-context' +is non-nil. + +Place the buffer below the current window or wherever the user option +`denote-backlinks-display-buffer-action' specifies." + (interactive) + (if-let* ((file buffer-file-name)) + (when-let* ((identifier (denote-retrieve-filename-identifier-with-error file))) + (funcall denote-query-links-buffer-function + identifier nil + (denote--backlinks-get-buffer-name file identifier) + denote-backlinks-display-buffer-action)) + (user-error "Buffer `%s' is not associated with a file" (current-buffer)))) + +(defalias 'denote-show-backlinks-buffer 'denote-backlinks + "Alias for `denote-backlinks' command.") + + +(defun denote-link-return-backlinks (&optional file) + "Return list of backlinks in current or optional FILE. +Also see `denote-link-return-links'." + (when-let* ((current-file (or file (buffer-file-name))) + (id (denote-retrieve-filename-identifier-with-error current-file))) + (delete current-file (denote-retrieve-files-xref-query id)))) + +;; TODO 2024-09-04: Instead of using `denote-link-return-backlinks' we +;; should have a function that does not try to find all backlinks but +;; simply exits as soon as it finds one. +(defun denote--file-has-backlinks-p (file) + "Return non-nil if FILE has backlinks." + (not (zerop (length (denote-link-return-backlinks file))))) + +;;;###autoload +(defun denote-find-backlink () + "Use minibuffer completion to visit backlink to current file. +Alo see `denote-find-link'." + (declare (interactive-only t)) + (interactive) + (find-file + (denote-get-path-by-id + (denote-extract-id-from-string + (denote-select-linked-file-prompt + (or (denote-link-return-backlinks) + (user-error "No backlinks found"))))))) + +;;;;;; Query links + +(defvar denote-query-link-history nil + "Minibuffer history of `denote-query-link-prompt'.") + +(defun denote-query-link-prompt (&optional initial-query prompt-text) + "Prompt for query string. +With optional INITIAL-QUERY use it as the initial minibuffer text. With +optional PROMPT-TEXT use it in the minibuffer instead of the default +prompt. + +Previous inputs at this prompt are available for minibuffer completion +if the user option `denote-history-completion-in-prompts' is set to a +non-nil value." + (when (and initial-query (string-empty-p initial-query)) + (setq initial-query nil)) + (denote--with-conditional-completion + 'denote-query-link-prompt + (format-prompt (or prompt-text "Query for") nil) + denote-query-link-history + initial-query)) + +(defconst denote-query-link-types '(query-contents query-filenames) + "Types of query links.") + +;; NOTE 2025-03-27: Should we expose a user option for this? And/or +;; should we add a DESCRIPTION parameter to `denote--format-query-link'? +;; +;; What would make for a good default description in that scenario? +;; Maybe "QC:query text here" and "QF:query text here" for +;; `query-contents' and `query-filenames' respectively. +(defvar denote-query-description-prefix "" + "Prefix string for query links to format their description text. +The description text constists of the value of this variable followed by +the query") + +(defun denote--format-query-link (type query file-type) + "Format QUERY link of TYPE for the given FILE-TYPE. +Return an error if TYPE is not one among the symbols specified in +`denote-query-link-types'. + +If FILE-TYPE is nil, use that of Org." + (unless (memq type denote-query-link-types) + (error "Type `%s' is not one among `denote-query-link-types'" type)) + (format (or (denote--link-format file-type) (denote--link-format 'org)) + (format "%s:%s" type query) + (format "%s%s" denote-query-description-prefix query))) + +;;;###autoload +(defun denote-query-contents-link (query) + "Insert query link for file contents. +Prompt for QUERY or use the text of the active region. When the user +follows this link, place any matches in a separate buffer (using the +built-in Xref mechanism). This is the equivalent of a Unix grep command +across the variable `denote-directory'." + (interactive + (list + (or (denote--get-active-region-content) + (denote-query-link-prompt nil "Query in file CONTENTS")))) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (denote--delete-active-region-content) + (insert (denote--format-query-link 'query-contents query (denote-filetype-heuristics buffer-file-name)))) + +;;;###autoload +(defun denote-query-filenames-link (query) + "Insert query link for file names. +Prompt for QUERY or use the text of the active region. When the user +follows this link, place any matches in a separate buffer (using the +built-in Dired mechanism). This is the equivalent of a Unix find +command across the variable `denote-directory'." + (interactive + (list + (or (denote--get-active-region-content) + (denote-query-link-prompt nil "Query in file NAMES")))) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (denote--delete-active-region-content) + (insert (denote--format-query-link 'query-filenames query (denote-filetype-heuristics buffer-file-name)))) + +(defvar denote--query-last-dired-buffer nil + "Buffer object produced by the last query for file names.") + +(defun denote--act-on-query-link (query) + "Act on QUERY link. +QUERY is a string of the form TYPE:SEARCH, where TYPE is one among +`denote-query-link-types' while SEARCH is the regular expression to +search for." + (cond + ((string-prefix-p "query-contents:" query) + (setq query (replace-regexp-in-string "query-contents:" "" query)) + (funcall denote-query-links-buffer-function query nil nil denote-query-links-display-buffer-action)) + ((string-prefix-p "query-filenames:" query) + (setq query (replace-regexp-in-string "query-filenames:" "" query)) + ;; NOTE 2025-03-27: I do not think we need to add another + ;; parameter to `denote-sort-dired' for handling the + ;; `display-buffer'. This is a special case, but we can always + ;; change it later if the need arises. + ;; + ;; Here we handle the buffer and window state to make it behave + ;; like the Xref buffer. Otherwise, Dired does not reuse its + ;; buffer (which is generally okay). + (let ((buffer (save-window-excursion (denote-sort-dired query nil nil nil)))) + (when (bufferp denote--query-last-dired-buffer) + (when-let* ((window (get-buffer-window denote--query-last-dired-buffer)) + (_ (window-live-p window))) + (delete-window window)) + (kill-buffer denote--query-last-dired-buffer)) + (display-buffer buffer denote-query-links-display-buffer-action) + (setq denote--query-last-dired-buffer buffer))) + (t + (error "Cannot open `%s' of unknown link type" query)))) + +;;;;; Link buttons + +(make-obsolete 'denote-link--find-file-at-button nil "4.0.0") + +(make-obsolete + 'denote-link-buttonize-buffer + 'denote-fontify-links-mode + "Use the `denote-fontify-links-mode', as it works better than buttonization. Since 3.0.0") + +;; NOTE 2025-03-24: This does not work for query links because of how +;; `markdown-follow-link-at-point' is implemented to always check for +;; links. +(defun denote-link-markdown-follow (link) + "Function to open Denote file present in LINK. +To be assigned to `markdown-follow-link-functions'." + (when (ignore-errors (string-match denote-id-regexp link)) + (funcall denote-open-link-function + (denote-get-path-by-id (match-string 0 link))))) + +(eval-after-load 'markdown-mode + '(add-hook 'markdown-follow-link-functions #'denote-link-markdown-follow)) + +;;;;; Link fontification + +;; TODO 2024-06-19: We need to bind RET and maybe even C-c C-o to a +;; command that opens the link at point. Then we may also rename this +;; keymap. +(defvar denote-link-mouse-map + (let ((map (make-sparse-keymap))) + (define-key map [mouse-2] #'denote-link-open-at-mouse) + (define-key map [mouse-3] #'denote-link-open-at-mouse) + (define-key map [follow-link] 'mouse-face) + map) + "Keymap for mouse actions over fontified Denote links.") + +(defun denote--link-open-at-point-subr () + "Open link at point." + (let ((query (get-text-property (point) 'denote-link-query-part))) + (if-let* ((path (denote-get-path-by-id query))) + (funcall denote-open-link-function path) + (denote--act-on-query-link query)))) + +(defun denote-link-open-at-point () + "Open Denote link at point." + (interactive) + (denote--link-open-at-point-subr)) + +(defun denote-link-open-at-mouse (ev) + "Open Denote link for mouse EV click." + (interactive "e") + (mouse-set-point ev) + (denote--link-open-at-point-subr)) + +(defun denote-get-link-face (query) + "Return appropriate face for QUERY." + (if (denote-identifier-p (string-trim-right query ":[^/]+.*")) + 'denote-faces-link + 'denote-faces-query-link)) + +(defun denote--fontify-links-subr (query limit) + "Do the work of the font-lock match for QUERY up to LIMIT. +Implementation based on the function `org-activate-links'." + (catch :exit + (while (re-search-forward query limit t) + (save-match-data ; to return the matches to font-lock + (let* ((start (match-beginning 0)) + (end (match-end 0)) + (visible-start (or (match-beginning 2) start)) + (visible-end (or (match-end 2) end)) + (query (match-string-no-properties 1))) + (let* ((properties `( face ,(denote-get-link-face query) + mouse-face highlight + keymap ,denote-link-mouse-map + denote-link-query-part ,query + help-echo query + htmlize-link (:uri ,query) + font-lock-multiline t)) + (non-sticky-props + '(rear-nonsticky (mouse-face highlight keymap invisible intangible help-echo htmlize-link))) + (face-property 'link) + (hidden (append '(invisible 'denote-link) properties))) + (remove-text-properties start end '(invisible nil)) + (add-text-properties start visible-start hidden) + (add-face-text-property start end face-property) + (add-text-properties visible-start visible-end properties) + (add-text-properties visible-end end hidden) + (dolist (pos (list end visible-start visible-end)) + (add-text-properties (1- pos) pos non-sticky-props))) + (throw :exit t)))) ; signal success + nil)) + +(defun denote-fontify-links (limit) + "Provide font-lock matcher to fontify links up to LIMIT." + (when-let* ((type (denote-filetype-heuristics (buffer-file-name)))) + (denote--fontify-links-subr (denote--link-in-context-regexp type) limit))) + +(define-obsolete-function-alias + 'denote-get-identifier-at-point + 'denote-get-link-identifier-or-query-term-at-point + "4.0.0") + +(defun denote-get-link-identifier-or-query-term-at-point (&optional point) + "Return the Denote identifier or query term at point or optional POINT." + (when-let* ((position (or point (point))) + (face-at-point (get-text-property position 'face)) + ((or (eq face-at-point 'denote-faces-link) + (member 'denote-faces-link face-at-point)))) + (or (get-text-property position 'denote-link-query-part) + (when-let* ((link-data (get-text-property position 'htmlize-link)) + (link (cadr link-data))) + (string-match denote-id-regexp link) + (match-string-no-properties 0 link))))) + +(defun denote--get-link-file-path-at-point (&optional point) + "Return link to the Denote file path at point or optional POINT. +To be used as a `thing-at' provider." + (when-let* ((position (or point (point))) + (id (get-text-property position 'denote-link-query-part)) + (path (denote-get-path-by-id id))) + (concat "file:" path))) + +(defvar thing-at-point-provider-alist) + +;;;###autoload +(defun denote-fontify-links-mode-maybe () + "Enable `denote-fontify-links-mode' in a denote file unless in `org-mode'." + (when (and buffer-file-name + (not (derived-mode-p 'org-mode)) + (denote-file-is-note-p buffer-file-name)) + (denote-fontify-links-mode))) + +;;;###autoload +(define-minor-mode denote-fontify-links-mode + "A minor mode to fontify and fold Denote links. + +Enabled this mode only when the current buffer is a Denote note and the +major mode is not `org-mode' (or derived therefrom). Consider using +`denote-fontify-links-mode-maybe' for this purpose." + :init-value nil + :global nil + :group 'denote + (require 'thingatpt) + (if denote-fontify-links-mode + (progn + (add-to-invisibility-spec 'denote-link) + (font-lock-add-keywords nil '((denote-fontify-links))) + (setq-local thing-at-point-provider-alist + (append thing-at-point-provider-alist + '((url . denote--get-link-file-path-at-point))))) + (remove-from-invisibility-spec 'denote-link) + (font-lock-remove-keywords nil '((denote-fontify-links))) + (setq-local thing-at-point-provider-alist + (delete + '(url . denote--get-link-file-path-at-point) + thing-at-point-provider-alist))) + (font-lock-update)) + +;;;;; Add links matching regexp + +(defvar denote-link--prepare-links-format "- %s\n" + "Format specifiers for `denote-add-links'.") + +(make-obsolete-variable 'denote-link-add-links-sort nil "3.1.0") + +(defun denote-link--prepare-links (files current-file-type id-only &optional no-sort include-date) + "Prepare links to FILES from CURRENT-FILE-TYPE. +When ID-ONLY is non-nil, use a generic link format. + +With optional NO-SORT do not try to sort the inserted lines. +Otherwise sort lines while accounting for `denote-link-add-links-sort'. + +Optional INCLUDE-DATE has the same meaning as in `denote-format-link'." + (let ((links)) + (dolist (file files) + (let* ((description (denote-get-link-description file)) + (link (denote-format-link file description current-file-type id-only include-date)) + (link-as-list-item (format denote-link--prepare-links-format link))) + (push link-as-list-item links))) + (if no-sort + (nreverse links) + (sort links #'string-collate-lessp)))) + +(defun denote-link--insert-links (files current-file-type &optional id-only no-sort include-date) + "Insert at point a typographic list of links matching FILES. + +With CURRENT-FILE-TYPE as a symbol among those specified in variable +`denote-file-type' (or the `car' of each element in `denote-file-types'), +format the link accordingly. With a nil or unknown non-nil value, +default to the Org notation. + +With ID-ONLY as a non-nil value, produce links that consist only +of the identifier, thus deviating from CURRENT-FILE-TYPE. + +Optional NO-SORT is passed to `denote-link--prepare-links'. + +Optional INCLUDE-DATE has the same meaning as in `denote-format-link'." + (when-let* ((links (denote-link--prepare-links files current-file-type id-only no-sort include-date))) + (dolist (link links) + (insert link)))) + +;;;###autoload +(defun denote-add-links (regexp &optional id-only) + "Insert links to all files whose file names match REGEXP. +Use this command to reference multiple files at once. Particularly +useful for the creation of metanotes (read the manual for more on the +matter). + +Optional ID-ONLY has the same meaning as in `denote-link': it +inserts links with just the identifier." + (interactive + (list + (denote-files-matching-regexp-prompt "Insert links to files matching REGEXP") + current-prefix-arg)) + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The current file type is not recognized by Denote")) + (let ((file-type (denote-filetype-heuristics (buffer-file-name)))) + (if-let* ((files (denote-directory-files regexp :omit-current))) + (denote-link--insert-links files file-type id-only) + (message "No links matching `%s'" regexp)))) + +;;;;; Link to file with matching contents + +;;;###autoload +(defun denote-link-to-file-with-contents (query &optional id-only) + "Link to a file whose contents match QUERY. +This is similar to `denote-link', except that the file prompt is limited +to files matching QUERY. Optional ID-ONLY has the same meaning as in +`denote-link'." + (interactive + (list (denote-query-link-prompt nil "Files whose contents include QUERY"))) + (if-let* ((files (denote-retrieve-files-xref-query query)) + ;; NOTE 2025-03-29: Maybe we should have a named prompt + ;; for this case, but I think we do not need it right now. + (file (completing-read + (format "Select FILE with contents `%s': " + (propertize query 'face 'denote-faces-prompt-current-name)) + (denote--completion-table 'file files) + nil t nil 'denote-file-history))) + (denote-link file + (denote-filetype-heuristics buffer-file-name) + (denote-get-link-description file) + id-only) + (user-error "No files include the query `%s' in their contents" query))) + +;;;###autoload +(defun denote-link-to-all-files-with-contents (query &optional id-only) + "Link to all files whose contents match QUERY. +This is similar to `denote-add-links', except it searches inside file +contents, not file names. Optional ID-ONLY has the same meaning as in +`denote-link' and `denote-add-links'." + (interactive + (list (denote-query-link-prompt nil "Files whose contents include QUERY"))) + (if-let* ((files (denote-retrieve-files-xref-query query))) + (denote-link--insert-links files (denote-filetype-heuristics buffer-file-name) id-only) + (user-error "No files include the query `%s' in their contents" query))) + +;;;;; Links from Dired marks + +;; NOTE 2022-07-21: I don't think we need a history for this one. +(defun denote-link--buffer-file-prompt (buffer-file-names) + "Select file from BUFFER-FILE-NAMES of Denote notes." + (let ((relative-buffer-file-names (mapcar #'denote-get-file-name-relative-to-denote-directory buffer-file-names))) + (concat (denote-directory) + (completing-read + "Select open note to add links to: " + (denote--completion-table 'file relative-buffer-file-names) + nil t)))) + +(defun denote-link--map-over-notes () + "Return list of `denote-file-has-denoted-filename-p' from Dired marked items." + (seq-filter (lambda (file) (and (denote-file-has-denoted-filename-p file) + (denote-file-has-identifier-p file))) + (dired-get-marked-files))) + +;;;###autoload +(defun denote-link-dired-marked-notes (files buffer &optional id-only) + "Insert Dired marked FILES as links in BUFFER. + +FILES conform with the Denote file-naming scheme, such that they can be +linked to using the `denote:' link type. + +The BUFFER is one which visits a Denote note file. If there are +multiple BUFFER candidates in buffers, prompt with completion for +one among them. If there is none, throw an error. + +With optional ID-ONLY as a prefix argument, insert links with +just the identifier (same principle as with `denote-link'). + +This command is meant to be used from a Dired buffer." + (interactive + (if (derived-mode-p 'dired-mode) + (list + (denote-link--map-over-notes) + (let ((file-names (denote--buffer-file-names))) + (find-buffer-visiting + (cond + ((null file-names) + (user-error "No buffers visiting Denote notes")) + ((eq (length file-names) 1) + (car file-names)) + (t + (denote-link--buffer-file-prompt file-names))))) + current-prefix-arg) + (user-error "This command only works inside a Dired buffer")) + dired-mode) + (when (null files) + (user-error "No note files to link to")) + (unless (buffer-live-p buffer) + (error "The buffer `%s' is not live" buffer)) + (let ((body (lambda () + (unless (or (denote--file-type-org-extra-p) + (and buffer-file-name (denote-file-has-supported-extension-p buffer-file-name))) + (user-error "The target file's type is not recognized by Denote")) + (when (y-or-n-p (format "Create links at point in `%s'?" buffer)) + (denote-link--insert-links files (denote-filetype-heuristics buffer-file-name) id-only) + (message "Added links to `%s'; displaying it now" + ;; TODO 2024-12-26: Do we need our face here? I think + ;; not, but let me keep a note of it. + (propertize (format "%s" buffer) 'face 'success)))))) + (if-let* ((window (get-buffer-window buffer)) + ((window-live-p window))) + (with-selected-window window (funcall body)) + (with-current-buffer buffer (funcall body)) + (display-buffer-below-selected buffer nil)))) + +(defalias 'denote-dired-link-marked-notes 'denote-link-dired-marked-notes + "Alias for `denote-link-dired-marked-notes' command.") + +;;;; Define menu + +(defvar denote--menu-contents + '(["Create a note" denote + :help "Create a new note in the `denote-directory'"] + ["Create a note with given file type" denote-type + :help "Create a new note with a given file type in the `denote-directory'"] + ["Create a note in subdirectory" denote-subdirectory + :help "Create a new note in a subdirectory of the `denote-directory'"] + ["Create a note with date" denote-date + :help "Create a new note with a given date in the `denote-directory'"] + ["Create a note with signature" denote-signature + :help "Create a new note with a given signature in the `denote-directory'"] + ["Open a note or create it if missing" denote-open-or-create + :help "Open an existing note in the `denote-directory' or create it if missing"] + ["Open a note or create it with the chosen command" denote-open-or-create-with-command + :help "Open an existing note or create it with the chosen command if missing"] + "---" + ["Rename a file" denote-rename-file + :help "Rename file interactively" + :enable (derived-mode-p 'dired-mode 'text-mode)] + ["Rename this file using its front matter" denote-rename-file-using-front-matter + :help "Rename the current file using its front matter as input" + :enable (derived-mode-p 'text-mode)] + ["Rename Dired marked files interactively" denote-dired-rename-files + :help "Rename marked files in Dired by prompting for all file name components" + :enable (derived-mode-p 'dired-mode)] + ["Rename Dired marked files with keywords" denote-dired-rename-marked-files-with-keywords + :help "Rename marked files in Dired by prompting for keywords" + :enable (derived-mode-p 'dired-mode)] + ["Rename Dired marked files using their front matter" denote-dired-rename-marked-files-using-front-matter + :help "Rename marked files in Dired using their front matter as input" + :enable (derived-mode-p 'dired-mode)] + "---" + ["Insert a direct link" denote-link + :help "Insert link to a file in the `denote-directory'" + :enable (derived-mode-p 'text-mode)] + ["Insert a direct link to file with contents" denote-link-to-file-with-contents + :help "Insert link to a file in the `denote-directory' whose contents include a query" + :enable (derived-mode-p 'text-mode)] + ["Insert a query link for file contents" denote-query-contents-link + :help "Insert query link searching for file contents in the `denote-directory'" + :enable (derived-mode-p 'text-mode)] + ["Insert a query link for file names" denote-query-filenames-link + :help "Insert query link searching for file names in the `denote-directory'" + :enable (derived-mode-p 'text-mode)] + "---" + ["Insert links to file names matching regexp" denote-add-links + :help "Insert links to file names in the `denote-directory' matching regexp" + :enable (derived-mode-p 'text-mode)] + ["Insert links to files whose contents match regexp" denote-link-to-all-files-with-contents + :help "Insert links to file in the `denote-directory' whose contents match regexp" + :enable (derived-mode-p 'text-mode)] + ["Insert Dired marked files as links" denote-link-dired-marked-notes + :help "Rename marked files in Dired as links in a Denote buffer" + :enable (derived-mode-p 'dired-mode)] + ["Show file backlinks" denote-backlinks + :help "Insert link to a file in the `denote-directory'" + :enable (derived-mode-p 'text-mode)] + ["Link to existing note or newly created one" denote-link-or-create + :help "Insert a link to an existing file, else create it and link to it" + :enable (derived-mode-p 'text-mode)] + ["Create note in the background and link to it directly" denote-link-after-creating + :help "Create new note and link to it from the current file" + :enable (derived-mode-p 'text-mode)] + ["Create note in the background with chosen command and link to it directly" denote-link-after-creating-with-command + :help "Create new note with the chosen command and link to it from the current file" + :enable (derived-mode-p 'text-mode)] + "---" + ["Generate sorted and filtered Dired listing" denote-sort-dired + :help "Generate a sorted and filtered Dired listing of files in the `denote-directory'"] + "---" + ["Highlight Dired file names" denote-dired-mode + :help "Apply colors to Denote file name components in Dired" + :enable (derived-mode-p 'dired-mode) + :style toggle + :selected (bound-and-true-p denote-dired-mode)]) + "Contents of the Denote menu.") + +(defun denote--menu-bar-enable () + "Enable Denote menu bar." + (define-key-after global-map [menu-bar denote] + (easy-menu-binding + (easy-menu-create-menu "Denote" denote--menu-contents) "Denote") + "Tools")) + +;; Enable Denote menu bar by default +(denote--menu-bar-enable) + +;;;###autoload +(define-minor-mode denote-menu-bar-mode "Show Denote menu bar." + :global t + :init-value t + (if denote-menu-bar-mode + (denote--menu-bar-enable) + (define-key global-map [menu-bar denote] nil))) + +(defun denote-context-menu (menu _click) + "Populate MENU with Denote commands at CLICK." + (define-key menu [denote-separator] menu-bar-separator) + (let ((easy-menu (make-sparse-keymap "Denote"))) + (easy-menu-define nil easy-menu nil + denote--menu-contents) + (dolist (item (reverse (lookup-key easy-menu [menu-bar]))) + (when (consp item) + (define-key menu (vector (car item)) (cdr item))))) + menu) + +;;;; Register `denote:' custom Org hyperlink + +(declare-function org-link-open-as-file "ol" (path arg)) + +(defun denote-link--ol-resolve-link-to-target (link &optional full-data) + "Resolve LINK to target file, with or without additioanl file-search terms. +With optional FULL-DATA return a list in the form of (path query file-search)." + (let* ((file-search (and (string-match "::\\(.*\\)\\'" link) + (match-string 1 link))) + (query (if (and file-search (not (string-empty-p file-search))) + (substring link 0 (match-beginning 0)) + link)) + (path (denote-get-path-by-id query))) + (cond + (full-data + (list path query file-search)) + ((and file-search (not (string-empty-p file-search))) + (concat path "::" file-search)) + (t (or path query))))) + +;;;###autoload +(defun denote-link-ol-follow (link) + "Find file of type `denote:' matching LINK. +LINK is the identifier of the note, optionally followed by a file search +option akin to that of standard Org `file:' link types. Read Info +node `(org) Query Options'. + +If LINK is not an identifier, then it is not pointing to a file but to a +query of file contents or file names (see the commands +`denote-query-contents-link' and `denote-query-filenames-link'). + +Uses the function `denote-directory' to establish the path to the file." + (if-let* ((match (denote-link--ol-resolve-link-to-target link)) + (_ (file-exists-p (string-trim-right match ":[^/]+.*")))) + (org-link-open-as-file match nil) + (denote--act-on-query-link match))) + +;;;###autoload +(defun denote-link-ol-complete () + "Like `denote-link' but for Org integration. +This lets the user complete a link through the `org-insert-link' +interface by first selecting the `denote:' hyperlink type." + (if-let* ((file (denote-file-prompt))) + (concat "denote:" (denote-retrieve-filename-identifier file)) + (user-error "No files in `denote-directory'"))) + +(declare-function org-link-store-props "ol.el" (&rest plist)) +(defvar org-store-link-plist) + +(declare-function org-entry-put "org" (pom property value)) +(declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) +(declare-function org-id-new "org-id" (&optional prefix)) + +(defun denote-link-ol-get-id () + "Get the CUSTOM_ID of the current entry. +If the entry already has a CUSTOM_ID, return it as-is, else +create a new one." + (let* ((pos (point)) + (id (org-entry-get pos "CUSTOM_ID"))) + (if (and (stringp id) (string-match-p "\\S-" id)) + id + (setq id (org-id-new "h")) + (org-entry-put pos "CUSTOM_ID" id) + id))) + +(declare-function org-get-heading "org" (no-tags no-todo no-priority no-comment)) + +(defun denote-link-ol-get-heading () + "Get current Org heading text." + (org-get-heading :no-tags :no-todo :no-priority :no-comment)) + +(defun denote-link-format-heading-description (file-text heading-text) + "Return description for FILE-TEXT with HEADING-TEXT at the end." + (format "%s::%s" file-text heading-text)) + +;;;###autoload +(defun denote-link-ol-store (&optional interactive?) + "Handler for `org-store-link' adding support for denote: links. +Optional INTERACTIVE? is used by `org-store-link'. + +Also see the user option `denote-org-store-link-to-heading'." + (when interactive? + (when-let* ((file (buffer-file-name)) + ((denote-file-is-note-p file)) + (file-id (denote-retrieve-filename-identifier file)) + (description (denote-get-link-description file))) + (let ((heading-links (and denote-org-store-link-to-heading + (derived-mode-p 'org-mode) + (denote--org-capture-link-specifiers-p))) + (heading (denote-link-ol-get-heading))) + (org-link-store-props + :type "denote" + :description (if (and heading-links heading) + (denote-link-format-heading-description + description + heading) + description) + :link (cond + ((when-let* ((id (org-entry-get (point) "CUSTOM_ID"))) + (format "denote:%s::#%s" file-id id))) + ((and heading-links (eq denote-org-store-link-to-heading 'context) heading) + (format "denote:%s::*%s" file-id heading)) + ((and heading-links heading) + (format "denote:%s::#%s" file-id (denote-link-ol-get-id))) + (t + (concat "denote:" file-id)))) + org-store-link-plist)))) + +;;;###autoload +(defun denote-link-ol-export (link description format) + "Export a `denote:' link from Org files. +The LINK, DESCRIPTION, and FORMAT are handled by the export +backend." + (pcase-let* ((`(,path ,query ,file-search) (denote-link--ol-resolve-link-to-target link :full-data)) + (anchor (when path (file-relative-name (file-name-sans-extension path)))) + (desc (cond + (description) + (file-search (format "denote:%s::%s" query file-search)) + (t (concat "denote:" query))))) + (if path + (pcase format + ('html (if file-search + (format "%s" anchor file-search desc) + (format "%s" anchor desc))) + ('latex (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc)) + ('texinfo (format "@uref{%s,%s}" path desc)) + ('ascii (format "[%s] " desc path)) + ('md (format "[%s](%s)" desc path)) + (_ path)) + (format-message "[[Denote query for `%s']]" query)))) + +(defun denote-link-ol-help-echo (_window _object position) + "Echo the full file path of the identifier at POSITION." + (when-let* ((htmlize-link (get-text-property position 'htmlize-link)) + (string (plist-get htmlize-link :uri)) + (identifier (replace-regexp-in-string "denote:\\(.*?\\)\\(#.*\\)?" "\\1" string)) + (path (denote-get-path-by-id identifier))) + path)) + +;; The `eval-after-load' part with the quoted lambda is adapted from +;; Elfeed: . + +;;;###autoload +(eval-after-load 'org + `(funcall + ;; The extra quote below is necessary because uncompiled closures + ;; do not evaluate to themselves. The quote is harmless for + ;; byte-compiled function objects. + ',(lambda () + (with-no-warnings + (org-link-set-parameters + "denote" + :follow #'denote-link-ol-follow + :face #'denote-get-link-face + :help-echo #'denote-link-ol-help-echo + :complete #'denote-link-ol-complete + :store #'denote-link-ol-store + :export #'denote-link-ol-export))))) + +;;;; Glue code for org-capture + +(defgroup denote-org-capture () + "Integration between Denote and Org Capture." + :group 'denote) + +(defcustom denote-org-capture-specifiers "%l\n%i\n%?" + "String with format specifiers for `org-capture-templates'. +Check that variable's documentation for the details. + +The string can include arbitrary text. It is appended to new +notes via the `denote-org-capture' function. Every new note has +the standard front matter we define." + :type 'string + :package-version '(denote . "0.1.0") + :group 'denote-org-capture) + +(defun denote--org-capture-link-specifiers-p () + "Return non-nil if `denote-org-capture-specifiers' uses link specifiers." + (when (stringp denote-org-capture-specifiers) + (string-match-p "%^?[aAlL]" denote-org-capture-specifiers))) + +(defvar denote-last-path nil "Store last path.") + +;;;###autoload +(defun denote-org-capture () + "Create new note through `org-capture-templates'. +Use this as a function that returns the path to the new file. +The file is populated with Denote's front matter. It can then be +expanded with the usual specifiers or strings that +`org-capture-templates' supports. + +This function obeys `denote-prompts', but it ignores `file-type', +if present: it always sets the Org file extension for the created +note to ensure that the capture process works as intended, +especially for the desired output of the +`denote-org-capture-specifiers' (which can include arbitrary +text). + +Consult the manual for template samples." + (pcase-let* ((denote-prompts (remove 'file-type denote-prompts)) ; Do not prompt for file-type. We use org. + (`(,title ,keywords _ ,directory ,date ,template ,signature) + (denote--creation-get-note-data-from-prompts)) + (`(,title ,keywords _ ,directory ,date ,id ,template ,signature) + (denote--creation-prepare-note-data title keywords 'org directory date template signature)) + (front-matter (denote--format-front-matter title date keywords id signature 'org)) + (template-string (cond ((stringp template) template) + ((functionp template) (funcall template)) + (t (user-error "Invalid template"))))) + (setq denote-last-path + (denote-format-file-name directory id keywords title ".org" signature)) + (when (file-regular-p denote-last-path) + (user-error "A file named `%s' already exists" denote-last-path)) + (denote--keywords-add-to-history keywords) + (concat front-matter template-string denote-org-capture-specifiers))) + +;; TODO 2023-12-02: Maybe simplify `denote-org-capture-with-prompts' +;; by passing a single PROMPTS that is the same value as `denote-prompts'? + +;;;###autoload +(defun denote-org-capture-with-prompts (&optional title keywords subdirectory date template) + "Like `denote-org-capture' but with optional prompt parameters. + +When called without arguments, do not prompt for anything. Just +return the front matter with title and keyword fields empty and +the date and identifier fields specified. Also make the file +name consist of only the identifier plus the Org file name +extension. + +Otherwise produce a minibuffer prompt for every non-nil value +that corresponds to the TITLE, KEYWORDS, SUBDIRECTORY, DATE, and +TEMPLATE arguments. The prompts are those used by the standard +`denote' command and all of its utility commands. + +When returning the contents that fill in the Org capture +template, the sequence is as follows: front matter, TEMPLATE, and +then the value of the user option `denote-org-capture-specifiers'. + +Important note: in the case of SUBDIRECTORY actual subdirectories +must exist---Denote does not create them. Same principle for +TEMPLATE as templates must exist and are specified in the user +option `denote-templates'." + (let ((denote-prompts '())) + (when template (push 'template denote-prompts)) + (when date (push 'date denote-prompts)) + (when subdirectory (push 'subdirectory denote-prompts)) + (when keywords (push 'keywords denote-prompts)) + (when title (push 'title denote-prompts)) + (denote-org-capture))) + +(defun denote-org-capture-delete-empty-file () + "Delete file if capture with `denote-org-capture' is aborted." + (when-let* ((file denote-last-path) + ((denote--file-empty-p file))) + (delete-file denote-last-path))) + +(add-hook 'org-capture-after-finalize-hook #'denote-org-capture-delete-empty-file) + +;;;; The `denote-rename-buffer-mode' + +(defgroup denote-rename-buffer nil + "Rename Denote buffers to be shorter and easier to read." + :group 'denote + :link '(info-link "(denote) Top") + :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) + +(defvaralias 'denote-buffer-has-backlinks-string 'denote-rename-buffer-backlinks-indicator + "Alias for `denote-rename-buffer-backlinks-indicator'.") + +(defcustom denote-rename-buffer-backlinks-indicator " <-->" + "A string used to indicate that a buffer has backlinks pointing to it." + :type 'string + :package-version '(denote . "3.1.0") + :group 'denote-rename-buffer) + +(defcustom denote-rename-buffer-format "[D] %D%b" + "The format of the buffer name `denote-rename-buffer' should use. +The value is a string that treats specially the following specifiers: + +- The %t is the Denote TITLE in the front matter or the file name. +- The %T is the Denote TITLE in the file name. +- The %i is the Denote IDENTIFIER of the file. +- The %I is the identifier converted to DAYNAME, DAYNUM MONTHNUM YEAR. +- The %d is the same as %i (DATE mnemonic). +- The %D is a \"do what I mean\" which behaves the same as %t and if + that returns nothing, it falls back to %I, then %i. +- The %s is the Denote SIGNATURE of the file. +- The %k is the Denote KEYWORDS of the file. +- The %b inserts `denote-rename-buffer-backlinks-indicator'. +- The %% is a literal percent sign. + +In addition, the following flags are available for each of the specifiers: + +- 0 :: Pad to the width, if given, with zeros instead of spaces. +- - :: Pad to the width, if given, on the right instead of the left. +- < :: Truncate to the width and precision, if given, on the left. +- > :: Truncate to the width and precision, if given, on the right. +- ^ :: Convert to upper case. +- _ :: Convert to lower case. + +When combined all together, the above are written thus: + + %SPECIFIER-CHARACTER + +Any other string it taken as-is. Users may want, for example, to +include some text that makes Denote buffers stand out, such as +a [D] prefix." + :type 'string + :package-version '(denote . "4.0.0") + :group 'denote-rename-buffer) + +(defcustom denote-rename-buffer-function #'denote-rename-buffer + "Symbol of function that is called to rename the Denote file buffer. +The default `denote-rename-buffer' function uses the pattern +described in `denote-rename-buffer-format'. + +Users can set this variable to an arbitrary function that does +something else. The function is called without arguments from +the `find-file-hook' and `denote-after-new-note-hook'. + +A nil value for this variable means that the title of the Denote +buffer will be used, if available." + :type '(choice + (const :tag "Rename using the `denote-rename-buffer-format'" denote-rename-buffer) + (function :tag "Use a custom renaming function")) + :package-version '(denote . "2.1.0") + :group 'denote-rename-buffer) + +(defun denote-rename-buffer--format (buffer) + "Parse the BUFFER through the `denote-rename-buffer-format'." + (when-let* ((file (buffer-file-name buffer))) + (let ((type (denote-filetype-heuristics file)) + (should-show-backlink-indicator (and ; only do search if format contains "%b" + (string-match-p "%b" denote-rename-buffer-format) + (denote--file-has-backlinks-p file)))) + (string-trim + (format-spec denote-rename-buffer-format + (list (cons ?t (cond + ((denote-retrieve-front-matter-title-value file type)) + ((denote-retrieve-filename-title file)) + (t ""))) + (cons ?T (or (denote-retrieve-filename-title file) "")) + (cons ?b (if should-show-backlink-indicator denote-rename-buffer-backlinks-indicator "")) + (cons ?i (or (denote-retrieve-filename-identifier file) "")) + ;; TODO 2025-04-03: Maybe we can have something like `denote-date-format' here, + ;; but I think we are okay with a hardcoded value. + (cons ?I (or (when-let* ((id (denote-retrieve-filename-identifier file)) + (_ (denote-valid-date-p id))) + (format-time-string "%A, %e %B %Y" (date-to-time (denote--id-to-date id)))) + "")) + (cons ?d (or (denote-retrieve-filename-identifier file) "")) + (cons ?D (cond + ((denote-retrieve-front-matter-title-value file type)) + ((denote-retrieve-filename-title file)) + ((when-let* ((id (denote-retrieve-filename-identifier file))) + (if (denote-valid-date-p id) + (format-time-string "%A, %e %B %Y" (date-to-time (denote--id-to-date id))) + id))) + (t ""))) + (cons ?s (or (denote-retrieve-filename-signature file) "")) + (cons ?k (or (denote-retrieve-filename-keywords file) "")) + (cons ?% "%")) + 'delete))))) + +(defun denote-rename-buffer (&optional buffer) + "Rename current buffer or optional BUFFER with `denote-rename-buffer-format'. +The symbol of this function is the default value of the user +option `denote-rename-buffer-function' and is thus used by the +`denote-rename-buffer-mode'." + (when-let* ((file (buffer-file-name buffer)) + ((denote-file-has-identifier-p file)) + (new-name (denote-rename-buffer--format (or buffer (current-buffer)))) + ((not (string-blank-p new-name)))) + (rename-buffer new-name :unique))) + +(defun denote-rename-buffer--fallback (&optional buffer) + "Fallback to rename BUFFER or `current-buffer'. +This is called if `denote-rename-buffer-rename-function' is nil." + (let ((denote-rename-buffer-format "%t")) + (denote-rename-buffer buffer))) + +(defun denote-rename-buffer-rename-function-or-fallback () + "Call `denote-rename-buffer-function' or its fallback to rename with title. +Add this to `find-file-hook' and `denote-after-new-note-hook'." + (funcall (or denote-rename-buffer-function #'denote-rename-buffer--fallback))) + +;;;###autoload +(define-minor-mode denote-rename-buffer-mode + "Automatically rename Denote buffers to be easier to read. +A buffer is renamed upon visiting the underlying file. This +means that existing buffers are not renamed until they are +visited again in a new buffer (files are visited with the command +`find-file' or related)." + :global t + (if denote-rename-buffer-mode + (progn + (add-hook 'denote-after-new-note-hook #'denote-rename-buffer-rename-function-or-fallback) + (add-hook 'denote-after-rename-file-hook #'denote-rename-buffer-rename-function-or-fallback) + (add-hook 'find-file-hook #'denote-rename-buffer-rename-function-or-fallback)) + (remove-hook 'denote-after-new-note-hook #'denote-rename-buffer-rename-function-or-fallback) + (remove-hook 'denote-after-rename-file-hook #'denote-rename-buffer-rename-function-or-fallback) + (remove-hook 'find-file-hook #'denote-rename-buffer-rename-function-or-fallback))) + +(provide 'denote) +;;; denote.el ends here blob - /dev/null blob + dd891fd99baccd292a21829dfff25ae0881f2082 (mode 644) Binary files /dev/null and elpa/denote-4.0.0/denote.info differ blob - /dev/null blob + f87165c99de687bfbadf03e7d99cf25a1a5a2530 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/dir @@ -0,0 +1,19 @@ +This is the file .../info/dir, which contains the +topmost node of the Info hierarchy, called (dir)Top. +The first time you invoke Info you start off looking at this node. + +File: dir, Node: Top This is the top of the INFO tree + + This (the Directory node) gives a menu of major topics. + Typing "q" exits, "H" lists all Info commands, "d" returns here, + "h" gives a primer for first-timers, + "mEmacs" visits the Emacs manual, etc. + + In Emacs, you can click mouse button 2 on a menu item or cross reference + to select it. + +* Menu: + +Emacs misc features +* Denote: (denote). Simple notes with an efficient file-naming + scheme. blob - /dev/null blob + 1025c345c029f57bf021a7791a18ed79ab2099a0 (mode 644) --- /dev/null +++ elpa/denote-4.0.0/tests/denote-test.el @@ -0,0 +1,545 @@ +;;; denote-test.el --- Unit tests for Denote -*- lexical-binding: t -*- + +;; Copyright (C) 2023-2025 Free Software Foundation, Inc. + +;; Author: Protesilaos Stavrou +;; Maintainer: Protesilaos Stavrou +;; URL: https://github.com/protesilaos/denote + +;; This file is NOT part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Tests for Denote. Note that we are using Shorthands in this file, +;; so the "dt-" prefix really is "denote-test-". Evaluate the +;; following to learn more: +;; +;; (info "(elisp) Shorthands") + +;;; Code: + +(require 'ert) + +;;;; Tests for denote.el + +(require 'denote) + +(ert-deftest dt-denote--make-denote-directory () + "Test that `denote--make-denote-directory' creates the directory." + (should (null (denote--make-denote-directory)))) + +(ert-deftest dt-denote-directory () + "Test that variable `denote-directory' returns an absolute directory name." + (let ((path (denote-directory))) + (should (and (file-directory-p path) + (file-name-absolute-p path))))) + +(ert-deftest dt-denote-sluggify-title () + "Test that `denote-sluggify-title' removes punctuation from the string. +Concretely, remove anything specified in `denote-sluggify-title'." + (should (equal (denote-sluggify-title "this-is-!@#test") + "this-is-test"))) + +(ert-deftest dt-denote-slug-keep-only-ascii () + "Test that `denote-slug-keep-only-ascii' removes non-ASCII characters." + (should (equal + (denote-slug-keep-only-ascii "There are no-ASCII : characters | here 😀") + "There are no-ASCII characters here "))) + +(ert-deftest dt-denote-slug-hyphenate () + "Test that `denote-slug-hyphenate' hyphenates the string. +Also replace multiple hyphens with a single one and remove any +leading and trailing hyphen." + (should (equal (denote-slug-hyphenate "__ This is a test __ ") + "This-is-a-test"))) + +(ert-deftest dt-denote-sluggify () + "Test that `denote-sluggify' sluggifies the string. +To sluggify is to (i) downcase, (ii) hyphenate, (iii) de-punctuate, and (iv) remove spaces from the string." + (should (equal (denote-sluggify 'title " ___ !~!!$%^ This iS a tEsT ++ ?? ") + "this-is-a-test"))) + +(ert-deftest Ddenote--slug-put-equals () + "Test that `denote-slug-put-equals' replaces spaces/underscores with =. +Otherwise do the same as what is described in +`dt-denote-slug-hyphenate'. + +The use of the equals sign is for the SIGNATURE field of the +Denote file name." + (should (equal (denote-slug-put-equals "__ This is a test __ ") + "This=is=a=test"))) + +(ert-deftest dt-denote-sluggify-signature () + "Test that `denote-sluggify-signature' sluggifies the string for file signatures. +This is like `dt-denote-sluggify', except that it also +accounts for what we describe in `dt-denote-slug-put-equals'." + (should (equal (denote-sluggify-signature "--- ___ !~!!$%^ This -iS- a tEsT ++ ?? ") + "this=is=a=test"))) + +(ert-deftest dt-denote-sluggify-keyword () + "Test that `denote-sluggify-keyword' sluggifies the string while joining words. +In this context, to join words is to elimitate any space or +delimiter between them. + +Otherwise, this is like `dt-denote-sluggify'." + (should (equal (denote-sluggify-keyword "--- ___ !~!!$%^ This iS a - tEsT ++ ?? ") + "thisisatest"))) + +(ert-deftest dt-denote-sluggify-keywords () + "Test that `denote-sluggify-keywords' sluggifies a list of strings. +The function also account for the value of the user option +`denote-allow-multi-word-keywords'." + (should + (equal (denote-sluggify-keywords '("one !@# --- one" " two" "__ three __")) + '("oneone" "two" "three")))) + +(ert-deftest dt-denote--file-empty-p () + "Test that `denote--file-empty-p' returns non-nil on empty file." + ;; (should (null (denote--file-empty-p user-init-file)) + (should (let ((file (make-temp-file "denote-test"))) + (prog1 + (denote--file-empty-p file) + (delete-file file))))) + +(ert-deftest dt-denote-file-is-note-p () + "Test that `denote-file-is-note-p' checks that files is a Denote note. +For our purposes, a note must note be a directory, must satisfy +`file-regular-p', its path must be part of the variable +`denote-directory', it must have a Denote identifier in its name, +and use one of the extensions implied by the variable `denote-file-type'." + (should (let* ((tmp (temporary-file-directory)) + (denote-directory tmp) + (file (concat tmp "20230522T154900--test__keyword.txt"))) + (with-current-buffer (find-file-noselect file) + (write-file file)) + (prog1 + (denote-file-is-note-p file) + (delete-file file))))) + +(ert-deftest dt-denote-file-has-identifier-p () + "Test that `denote-file-has-identifier-p' checks for a Denote identifier." + (should (denote-file-has-identifier-p "20230522T154900--test__keyword.txt")) + (should (null (denote-file-has-identifier-p "T154900--test__keyword.txt")))) + +(ert-deftest dt-denote-file-has-signature-p () + "Test that `denote-file-has-signature-p' checks for a Denote signature." + (should (denote-file-has-signature-p "20230522T154900==sig--test__keyword.txt")) + (should (null (denote-file-has-signature-p "20230522T154900--test__keyword.txt")))) + +(ert-deftest dt-denote-file-has-supported-extension-p () + "Test that `denote-file-has-supported-extension-p' matches a supported extension." + (should + (member + (file-name-extension "20230522T154900==sig--test__keyword.txt" :period) + (denote-file-type-extensions-with-encryption))) + (should + (null + (member + (file-name-extension "20230522T154900==sig--test__keyword" :period) + (denote-file-type-extensions-with-encryption))))) + +(ert-deftest dt-denote-file-type-extensions () + "Test that `denote-file-type-extensions' returns file extensions. +We check for the common file type extensions, though the user can +theoretically set `denote-file-types' to nil and handle things on +their own. We do not have to test for that scenario, because +such a user will be redefining large parts of Denote's behaviour +with regard to file types." + (let ((extensions (denote-file-type-extensions))) + (should (or (member ".md" extensions) + (member ".org" extensions) + (member ".txt" extensions))))) + +(ert-deftest dt-denote-file-type-extensions-with-encryption () + "Test that `denote-file-type-extensions-with-encryption' covers encryption. +Extend what we do in `dt-denote-file-type-extensions'." + (let ((extensions (denote-file-type-extensions-with-encryption))) + (should (or (member ".md" extensions) + (member ".org" extensions) + (member ".txt" extensions) + (member ".md.gpg" extensions) + (member ".org.gpg" extensions) + (member ".txt.gpg" extensions) + (member ".md.age" extensions) + (member ".org.age" extensions) + (member ".txt.age" extensions))))) + +(ert-deftest dt-denote--format-front-matter () + "Test that `denote--format-front-matter' formats front matter correctly. +To make the test reproducible, set `denote-date-format' to a value that +does not involve the time zone." + (let ((denote-date-format "%Y-%m-%d") + (denote-front-matter-components-present-even-if-empty-value '(title keywords signature date identifier))) + (should (and (equal (denote--format-front-matter "" (date-to-time "20240101T120000") '("") "" "" 'text) + (mapconcat #'identity + '("title: " + "date: 2024-01-01" + "tags: " + "identifier: " + "signature: " + "---------------------------\n\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" (date-to-time "2023-06-05") '("one" "two") + "20230605T102234" "sig" 'text) + (mapconcat #'identity + '("title: Some test" + "date: 2023-06-05" + "tags: one two" + "identifier: 20230605T102234" + "signature: sig" + "---------------------------\n\n") + "\n")))) + + (should (and (equal (denote--format-front-matter "" (date-to-time "20240101T120000") nil "" "" 'org) + (mapconcat #'identity + '("#+title: " + "#+date: 2024-01-01" + "#+filetags: " + "#+identifier: " + "#+signature: " + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" (date-to-time "2023-06-05") '("one" "two") + "20230605T102234" "sig" 'org) + (mapconcat #'identity + '("#+title: Some test" + "#+date: 2023-06-05" + "#+filetags: :one:two:" + "#+identifier: 20230605T102234" + "#+signature: sig" + "\n") + "\n")))) + + (should (and (equal (denote--format-front-matter "" (date-to-time "20240101T120000") nil "" "" 'markdown-yaml) + (mapconcat #'identity + '("---" + "title: \"\"" + "date: 2024-01-01" + "tags: []" + "identifier: \"\"" + "signature: \"\"" + "---" + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" (date-to-time "2023-06-05") '("one" "two") + "20230605T102234" "sig" 'markdown-yaml) + (mapconcat #'identity + '("---" + "title: \"Some test\"" + "date: 2023-06-05" + "tags: [\"one\", \"two\"]" + "identifier: \"20230605T102234\"" + "signature: \"sig\"" + "---" + "\n") + "\n")))) + + (should (and (equal (denote--format-front-matter "" (date-to-time "20240101T120000") nil "" "" 'markdown-toml) + (mapconcat #'identity + '("+++" + "title = \"\"" + "date = 2024-01-01" + "tags = []" + "identifier = \"\"" + "signature = \"\"" + "+++" + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" (date-to-time "2023-06-05") '("one" "two") + "20230605T102234" "sig" 'markdown-toml) + (mapconcat #'identity + '("+++" + "title = \"Some test\"" + "date = 2023-06-05" + "tags = [\"one\", \"two\"]" + "identifier = \"20230605T102234\"" + "signature = \"sig\"" + "+++" + "\n") + "\n")))))) + +(ert-deftest dt-denote-format-file-name () + "Test that `denote-format-file-name' returns all expected paths." + (let* ((title "Some test") + (id (format-time-string denote-id-format (denote-valid-date-p "2023-11-28 05:53:11"))) + (denote-directory "/tmp/test-denote") + (kws '("one" "two"))) + (should-error (denote-format-file-name + nil + id + kws + title + (denote--file-extension 'org) + "")) + + (should-error (denote-format-file-name + "" + id + kws + title + (denote--file-extension 'org) + "")) + + (should-error (denote-format-file-name + denote-directory ; notice this is the `let' bound value without the suffix + id + kws + title + (denote--file-extension 'org) + "")) + + (should-error (denote-format-file-name + (denote-directory) + "" + nil + "" + (denote--file-extension 'org) + "")) + + (should (equal (denote-format-file-name + (denote-directory) + nil + kws + title + (denote--file-extension 'org) + "") + "/tmp/test-denote/--some-test__one_two.org")) + + (should (equal (denote-format-file-name + (denote-directory) + "" + kws + title + (denote--file-extension 'org) + "") + "/tmp/test-denote/--some-test__one_two.org")) + + (should (equal (denote-format-file-name + (denote-directory) + "0123456" + kws + title + (denote--file-extension 'org) + "") + "/tmp/test-denote/@@0123456--some-test__one_two.org")) + + (should (equal (denote-format-file-name + (denote-directory) + id + kws + title + (denote--file-extension 'org) + "") + "/tmp/test-denote/20231128T055311--some-test__one_two.org")) + + (should (equal (denote-format-file-name + (denote-directory) + id + nil + "" + (denote--file-extension 'org) + "") + "/tmp/test-denote/20231128T055311.org")) + + (should (equal (denote-format-file-name + (denote-directory) + id + nil + nil + (denote--file-extension 'org) + nil) + "/tmp/test-denote/20231128T055311.org")) + + (should (equal (denote-format-file-name + (denote-directory) + id + kws + title + (denote--file-extension 'org) + "sig") + "/tmp/test-denote/20231128T055311==sig--some-test__one_two.org")))) + +(ert-deftest dt-denote-get-file-extension () + "Test that `denote-get-file-extension' gets the correct file extension." + (should (and (equal (denote-get-file-extension "20231010T105034--some-test-file__denote_testing") "") + (equal (denote-get-file-extension "20231010T105034--some-test-file__denote_testing.org") ".org") + (equal (denote-get-file-extension "20231010T105034--some-test-file__denote_testing.org.gpg") ".org.gpg") + (equal (denote-get-file-extension "20231010T105034--some-test-file__denote_testing.org.age") ".org.age")))) + +(ert-deftest dt-denote-get-file-extension-sans-encryption () + "Test that `denote-get-file-extension-sans-encryption' gets the file extension without encryption." + (should (and (equal (denote-get-file-extension-sans-encryption "20231010T105034--some-test-file__denote_testing") "") + (equal (denote-get-file-extension-sans-encryption "20231010T105034--some-test-file__denote_testing.org") ".org") + (equal (denote-get-file-extension-sans-encryption "20231010T105034--some-test-file__denote_testing.org.gpg") ".org") + (equal (denote-get-file-extension-sans-encryption "20231010T105034--some-test-file__denote_testing.org.age") ".org")))) + +(ert-deftest dt-denote-filetype-heuristics () + "Test that `denote-filetype-heuristics' gets the correct file type." + (should (and (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing") nil) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.org") 'org) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.org.gpg") 'org) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.org.age") 'org) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.txt") 'text) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.txt.gpg") 'text) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.txt.age") 'text) + ;; NOTE 2023-10-11: It returns `markdown-yaml' as a fallback. In + ;; an actual file, it reads the file contents to determine what + ;; it is and can return `markdown-toml'. In principle, we should + ;; be testing this here, though I prefer to keep things simple. + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.md") 'markdown-yaml) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.md.gpg") 'markdown-yaml) + (eq (denote-filetype-heuristics "20231010T105034--some-test-file__denote_testing.md.age") 'markdown-yaml)))) + +(ert-deftest dt-denote-get-identifier () + "Test that `denote-get-identifier' returns an identifier." + (should (and (equal (denote-get-identifier nil) "") + (equal (denote-get-identifier 1705644188) "20240119T080308") + (equal (denote-get-identifier '(26026 4251)) "20240119T080307")))) + +(ert-deftest dt-denote-retrieve-filename-identifier () + "Test that `denote-retrieve-filename-identifier' returns only the identifier." + (should (and (null + (denote-retrieve-filename-identifier "/path/to/testing/--this-is-a-test-reordered__denote_testing.org")) + (equal + (denote-retrieve-filename-identifier "/path/to/testing/20240610T194654--this-is-a-test-reordered__denote_testing.org") + "20240610T194654") + (equal + (denote-retrieve-filename-identifier "/path/to/testing/20240610T194654==signature--this-is-a-test-reordered__denote_testing.org") + "20240610T194654") + (equal + (denote-retrieve-filename-identifier "/path/to/testing/--this-is-a-test-reordered__denote_testing@@20240610T194654.org") + "20240610T194654") + (equal + (denote-retrieve-filename-identifier "/path/to/testing/__denote_testing--this-is-a-test-reordered@@20240610T194654.org") + "20240610T194654") + (equal + (denote-retrieve-filename-identifier "/path/to/testing/__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "20240610T194654") + (equal + (denote-retrieve-filename-identifier "/path/to/testing/==signature__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "20240610T194654")))) + +(ert-deftest dt-denote-retrieve-filename-title () + "Test that `denote-retrieve-filename-title' returns only the title." + (should (and (null + (denote-retrieve-filename-title "/path/to/testing/20240610T194654__denote_testing.org")) + (equal + (denote-retrieve-filename-title "/path/to/testing/20240610T194654--this-is-a-test-reordered__denote_testing.org") + "this-is-a-test-reordered") + (equal + (denote-retrieve-filename-title "/path/to/testing/20240610T194654==signature--this-is-a-test-reordered__denote_testing.org") + "this-is-a-test-reordered") + (equal + (denote-retrieve-filename-title "/path/to/testing/--this-is-a-test-reordered__denote_testing@@20240610T194654.org") + "this-is-a-test-reordered") + (equal + (denote-retrieve-filename-title "/path/to/testing/__denote_testing--this-is-a-test-reordered@@20240610T194654.org") + "this-is-a-test-reordered") + (equal + (denote-retrieve-filename-title "/path/to/testing/__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "this-is-a-test-reordered") + (equal + (denote-retrieve-filename-title "/path/to/testing/==signature__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "this-is-a-test-reordered")))) + +(ert-deftest dt-denote-retrieve-filename-keywords () + "Test that `denote-retrieve-filename-keywords' returns only the keywords." + (should (and (null + (denote-retrieve-filename-keywords "/path/to/testing/20240610T194654--this-is-a-test-reordered.org")) + (equal + (denote-retrieve-filename-keywords "/path/to/testing/20240610T194654--this-is-a-test-reordered__denote_testing.org") + "denote_testing") + (equal + (denote-retrieve-filename-keywords "/path/to/testing/20240610T194654==signature--this-is-a-test-reordered__denote_testing.org") + "denote_testing") + (equal + (denote-retrieve-filename-keywords "/path/to/testing/--this-is-a-test-reordered__denote_testing@@20240610T194654.org") + "denote_testing") + (equal + (denote-retrieve-filename-keywords "/path/to/testing/__denote_testing--this-is-a-test-reordered@@20240610T194654.org") + "denote_testing") + (equal + (denote-retrieve-filename-keywords "/path/to/testing/__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "denote_testing") + (equal + (denote-retrieve-filename-keywords "/path/to/testing/==signature__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "denote_testing")))) + +(ert-deftest dt-denote-retrieve-filename-signature () + "Test that `denote-retrieve-filename-signature' returns only the signature." + (should (and (null + (denote-retrieve-filename-signature "/path/to/testing/20240610T194654--this-is-a-test-reordered__denote_testing.org")) + (equal + (denote-retrieve-filename-signature "/path/to/testing/20240610T194654==signature--this-is-a-test-reordered__denote_testing.org") + "signature") + (equal + (denote-retrieve-filename-signature "/path/to/testing/--this-is-a-test-reordered==signature__denote_testing@@20240610T194654.org") + "signature") + (equal + (denote-retrieve-filename-signature "/path/to/testing/__denote_testing--this-is-a-test-reordered==signature@@20240610T194654.org") + "signature") + (equal + (denote-retrieve-filename-signature "/path/to/testing/__denote_testing@@20240610T194654--this-is-a-test-reordered==signature.org") + "signature") + (equal + (denote-retrieve-filename-signature "/path/to/testing/==signature__denote_testing@@20240610T194654--this-is-a-test-reordered.org") + "signature")))) + +(ert-deftest dt-denote-identifier-p () + "Test that `denote-identifier-p' works for Denote identifiers." + (should (and (denote-identifier-p "20240901T090910") + (null (denote-identifier-p "20240901T090910-not-identifier-format"))))) + +(ert-deftest dt-denote--id-to-date () + "Test that `denote--id-to-date' returns the date from an identifier." + (should (equal (denote--id-to-date "20240901T090910") "2024-09-01")) + (should-error (denote--id-to-date "20240901T090910-not-identifier-format"))) + +(ert-deftest dt-denote--date-convert () + "Test that `denote--date-convert' works with dates." + (should (and + (equal (denote--date-convert '(26454 45206 461174 657000) :list) + '(26454 45206 461174 657000)) + + (equal (denote--date-convert '(26454 45206 461174 657000) :string) + "2024-12-09 10:55:50") + + (equal (denote--date-convert nil :string) + "") + + (equal (denote--date-convert nil :list) + nil))) + (should-error (denote--date-convert '(26454 45206 461174 657000) :not-valid-type)) + (should-error (denote--date-convert nil :not-valid-type))) + +(provide 'denote-test) +;;; denote-test.el ends here + +;; Local Variables: +;; read-symbol-shorthands: (("dt" . "denote-test-")) +;; End: blob - /dev/null blob + 02a9d0609cc8457e6e5090424ec533b0e65a7c26 (mode 644) --- /dev/null +++ elpa/denote-4.0.0.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2025-04-15T23:10:04+0200 using EDDSA \ No newline at end of file blob - /dev/null blob + b25586baccfaf0140555f8c08a091c50ef46582d (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/.elpaignore @@ -0,0 +1,2 @@ +COPYING +doclicense.texi blob - /dev/null blob + 3fff0b808826c4f93d106ceea1e95751f6c6cd6c (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/README-elpa @@ -0,0 +1,737 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + DENOTE-ORG: EXTENSIONS TO BETTER INTEGRATE ORG + WITH DENOTE + + Protesilaos Stavrou + info@protesilaos.com + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +This manual, written by Protesilaos Stavrou, describes the customization +options for the Emacs package called `denote' (or `denote.el'), and +provides every other piece of information pertinent to it. + +The documentation furnished herein corresponds to stable version 0.1.0, +released on 2025-04-15. Any reference to a newer feature which does not +yet form part of the latest tagged commit, is explicitly marked as such. + +Current development target is 0.2.0-dev. + +⁃ Package name (GNU ELPA): `denote-org' +⁃ Official manual: +⁃ Git repository: +⁃ Backronym: Denote… Ordinarily Restricts Gyrations. + +If you are viewing the README.org version of this file, please note that +the GNU ELPA machinery automatically generates an Info manual out of it. + +Table of Contents +───────────────── + +1. COPYING +2. Overview +3. Use Org dynamic blocks +.. 1. Org dynamic blocks to insert links +.. 2. The Org dynamic block to insert missing links only +.. 3. The Org dynamic block to insert backlinks +.. 4. Org dynamic block to insert file contents +.. 5. Org dynamic block to insert Org files as headings +4. Create a note from the current Org subtree +5. Insert link to an Org file with a further pointer to a heading +.. 1. Backlinks for Org headings +6. Convert `denote:' links to `file:' links in Org and vice versa +7. Installation +.. 1. GNU ELPA package +.. 2. Manual installation +8. Sample configuration +9. Acknowledgements +10. GNU Free Documentation License +11. Indices +.. 1. Function index +.. 2. Variable index +.. 3. Concept index + + +1 COPYING +═════════ + + Copyright (C) 2022-2025 Free Software Foundation, Inc. + + Permission is granted to copy, distribute and/or modify + this document under the terms of the GNU Free + Documentation License, Version 1.3 or any later version + published by the Free Software Foundation; with no + Invariant Sections, with the Front-Cover Texts being “A + GNU Manual,” and with the Back-Cover Texts as in (a) + below. A copy of the license is included in the section + entitled “GNU Free Documentation License.” + + (a) The FSF’s Back-Cover Text is: “You have the freedom to + copy and modify this GNU manual.” + + +2 Overview +══════════ + + The `denote-org' package contains extra features that better integrate + Denote with Org mode. These used to be available as part of the main + `denote' package in a file called `denote-org-extras.el', but now live + in this standalone package to main things easier to maintain and + understand. + + With `denote-org', users have Org-specific extensions such as dynamic + blocks, links to headings, and splitting an Org subtree into its own + standalone file. The following sections cover the technicalities. + + +3 Use Org dynamic blocks +════════════════════════ + + Denote can optionally integrate with Org mode’s “dynamic blocks” + facility. This means that it can use special blocks that are evaluated + with `C-c C-x C-u' (`org-dblock-update') to generate their contents. + The following subsections describe the types of Org dynamic blocks + provided by Denote. + + • [Org dynamic blocks to insert links or backlinks] + • [Org dynamic block to insert file contents] + + A dynamic block gets its contents by evaluating a function that + corresponds to the type of block. The block type and its parameters + are stated in the opening `#+BEGIN' line. Typing `C-c C-x C-u' + (`org-dblock-update') with point on that line runs (or re-runs) the + associated function with the given parameters and populates the + block’s contents accordingly. + + Dynamic blocks are particularly useful for metanote entries that + reflect on the status of earlier notes (read the Denote manual’s + section about writing metanotes). + + The Org manual describes the technicalities of Dynamic Blocks. + Evaluate: + + ┌──── + │ (info "(org) Dynamic Blocks") + └──── + + +[Org dynamic blocks to insert links or backlinks] See section 3.1 + +[Org dynamic block to insert file contents] See section 3.4 + +3.1 Org dynamic blocks to insert links +────────────────────────────────────── + + The `denote-links' block can be inserted at point with the command + `denote-org-dblock-insert-links' or by manually including the + following in an Org file: + + ┌──── + │ #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :not-regexp :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :include-date nil + │ + │ #+END: + └──── + + + All the parameters except for `:regexp' are optional. + + The `denote-links' block is also registered as an option for the + command `org-dynamic-block-insert-dblock'. + + Type `C-c C-x C-u' (`org-dblock-update') with point on the `#+BEGIN' + line to update the block. + + • The `:regexp' parameter is mandatory. Its value is a string and its + behaviour is the same as that of the standard `denote-add-links' + command (part of the main `denote' package). Concretely, it produces + a typographic list of links to files matching the giving regular + expression. The value of the `:regexp' parameter may also be of the + form read by the `rx' macro (Lisp notation instead of a string), as + explained in the Emacs Lisp Reference Manual (evaluate this code to + read the documentation: `(info "(elisp) Rx Notation")'). Note that + you do not need to write an actual regular expression to get + meaningful results: even something like `_journal' will work to + include all files that have a `journal' keyword. + + • The `:not-regexp' parameter is optional. It is a regular expression + that applies after `:regexp' to filter out the matching files. + + • The `:excluded-dirs-regexp' is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the `denote-excluded-directories-regexp' user option (which + is part of the main `denote' package). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of `:excluded-dirs-regexp' is nil (the default), the value of + `denote-excluded-directories-regexp' is used (which is also nil by + default, meaning that all directories are included). When the value + of `excluded-dirs-regexp' is `t' or some other symbol, then the + `denote-excluded-directories-regexp' is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + + • The `:sort-by-component' parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among `title', `keywords', `signature', + `identifier'. When using the command + `denote-org-dblock-insert-files', this parameter is automatically + inserted together with the (`:regexp' parameter) and the user is + prompted for a file name component. + + • The `:reverse-sort' parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter `:sort-by-component', though it also combines with + it. + + • The `:id-only' parameter is optional. It accepts a `t' value, in + which case links are inserted without a description text but only + with the identifier of the given file. This has the same meaning as + with the `denote-link' command and related facilities (read the + Denote manual’s section about linking to other files in the + `denote-directory'). + + • The `:include-date' parameter controls whether to display the date + of the file name after the title. This is done when its value is + `t'. By default (a nil value), no date is shown. + + • An optional `:block-name' parameter can be specified with a string + value to add a `#+name' to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote’s + purview). + + In some workflows, users may want to have a separate block to see what + other links they are missing since they last updated the dynamic + block. We cover that case as well ([The Org dynamic block to insert + missing links only]). + + +[The Org dynamic block to insert missing links only] See section 3.2 + + +3.2 The Org dynamic block to insert missing links only +────────────────────────────────────────────────────── + + The `denote-missing-links' block is available with the command + `denote-org-dblock-insert-missing-links'. It is like the + aforementioned `denote-links' block, except it only lists links to + files that are not present in the current buffer ([Org dynamic blocks + to insert links]). The parameters are otherwise the same and are all + optional except for `:regexp': + + ┌──── + │ #+BEGIN: denote-missing-links :regexp "YOUR REGEXP HERE" :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :include-date nil + │ + │ #+END: + └──── + + + The `denote-missing-links' block is also registered as an option for + the command `org-dynamic-block-insert-dblock'. + + Remember to type `C-c C-x C-u' (`org-dblock-update') with point on the + `#+BEGIN' line to update the block. + + +[Org dynamic blocks to insert links] See section 3.1 + + +3.3 The Org dynamic block to insert backlinks +───────────────────────────────────────────── + + Apart from links to files matching a regular expression, we can also + produce a list of backlinks to the current file. The dynamic block can + be inserted at point with the command + `denote-org-dblock-insert-backlinks' or by manually writing this in an + Org file: + + ┌──── + │ #+BEGIN: denote-backlinks :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :this-heading-only nil :include-date nil + │ + │ #+END: + └──── + + + The `denote-backlinks' block is also registered as an option for the + command `org-dynamic-block-insert-dblock'. + + Remember to type `C-c C-x C-u' (`org-dblock-update') with point on the + `#+BEGIN' line to update the block. + + The parameters recognised by this dynamic block are almost the same as + that for inserting links ([Org dynamic blocks to insert links]). They + are all optional in this case and there is no parameter expecting a + regular expression for matching files to link to. + + Additionally, the `denote-backlinks' block also recognises the + `:this-heading-only' parameter. It determines if the backlinks are + about the file or the heading under which the dynamic block is + inserted ([Backlinks for Org headings]). When this parameter is + omitted or nil (the default), then the backlinks are about the whole + file, but if this parameter has a `t' value then the backlinks are + specifically for the heading ([Insert link to an Org file with a + further pointer to a heading]). + + +[Org dynamic blocks to insert links] See section 3.1 + +[Backlinks for Org headings] See section 5.1 + +[Insert link to an Org file with a further pointer to a heading] See +section 5 + + +3.4 Org dynamic block to insert file contents +───────────────────────────────────────────── + + Denote can optionally use Org’s dynamic blocks facility to produce a + section that lists entire file contents ([Use Org dynamic blocks]). + This works by instructing Org to match a regular expression of Denote + files, the same way we do with Denote links (read the Denote manual’s + section about inserting links that match a regular expression). + + This is useful to, for example, compile a dynamically concatenated + list of scattered thoughts on a given topic, like `^2023.*_emacs' for + a long entry that incorporates all the notes written in 2023 with the + keyword `emacs'. + + To produce such a block, call the command + `denote-org-dblock-insert-files' or manually write the following block + in an Org file and then type `C-c C-x C-u' (`org-dblock-update') on + the `#+BEGIN' line to run it (do it again to recalculate the block): + + ┌──── + │ #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :not-regexp nil :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil + │ + │ #+END: + └──── + + + All parameters are optional except for `:regexp'. + + The `denote-files' block is also registered as an option for the + command `org-dynamic-block-insert-dblock'. + + Remember to type `C-c C-x C-u' (`org-dblock-update') with point on the + `#+BEGIN' line to update the block. + + To fully control the output, include these additional optional + parameters, which are described further below: + + • The `:regexp' parameter is mandatory. Its value is a string, + representing a regular expression to match Denote file names. Its + value may also be an `rx' expression instead of a string, as noted + in the previous section ([Org dynamic blocks to insert links or + backlinks]). Note that you do not need to write an actual regular + expression to get meaningful results: even something like `_journal' + will work to include all files that have a `journal' keyword. + + • The `:not-regexp' parameter is optional. It is a regular expression + that applies after `:regexp' to filter out the matching files. + + • The `:excluded-dirs-regexp' is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the `denote-excluded-directories-regexp' user option (which + is part of the main `denote' package). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of `:excluded-dirs-regexp' is nil (the default), the value of + `denote-excluded-directories-regexp' is used (which is also nil by + default, meaning that all directories are included). When the value + of `excluded-dirs-regexp' is `t' or some other symbol, then the + `denote-excluded-directories-regexp' is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + + • The `:sort-by-component' parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among `title', `keywords', `signature', + `identifier'. When using the command + `denote-org-dblock-insert-files', this parameter is automatically + inserted together with the (`:regexp' parameter) and the user is + prompted for a file name component. + + • The `:reverse-sort' parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter `:sort-by-component', though it also combines with + it. + + • The `:file-separator' parameter is optional. If it is omitted, then + Denote will use no separator between the files it inserts. If the + value is `t' the `denote-org-dblock-file-contents-separator' is + applied at the end of each file: it introduces some empty lines and + a horizontal rule between them to visually distinguish individual + files. If the `:file-separator' value is a string, it is used as the + file separator (e.g. use `"\n"' to insert just one empty new line). + + • The `:no-front-matter' parameter is optional. When set to a `t' + value, Denote tries to remove front matter from the files it is + inserting in the dynamic block. The technique used to perform this + operation is by removing all lines from the top of the file until + the first empty line. This works with the default front matter that + Denote adds, but is not 100% reliable with all sorts of user-level + modifications and edits to the file. When the `:no-front-matter' is + set to a natural number, Denote will omit that many lines from the + top of the file. + + • The `:add-links' parameter is optional. When it is set to a `t' + value, all files are inserted as a typographic list and are indented + accordingly. The first line in each list item is a link to the file + whose contents are inserted in the following lines. When the value + is `id-only', then links are inserted without a description text but + only with the identifier of the given file. This has the same + meaning as with the `denote-link' command and related facilities + (those are explained at length in the Denote manual). Remember that + Org can fold the items in a typographic list the same way it does + with headings. So even long files can be presented in this format + without much trouble. + + • An optional `:block-name' parameter can be specified with a string + value to add a `#+name' to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote’s + purview). + + +[Use Org dynamic blocks] See section 3 + +[Org dynamic blocks to insert links or backlinks] See section 3.1 + + +3.5 Org dynamic block to insert Org files as headings +───────────────────────────────────────────────────── + + [ IMPORTANT NOTE: This dynamic block only works with Org files, + because it has to assume the Org notation in order to insert each + file’s contents as its own heading. ] + + As a variation of the previously covered block that inserts file + contents, we have the `denote-org-dblock-insert-files-as-headings' + command ([Org dynamic block to insert file contents]). It Turn the + `#+title' of each file into a top-level heading. Then it increments + all original headings in the file by one, so that they become + subheadings of what once was the `#+title'. Similarly, the + `#+filetags' of each file as tags for the top-level heading (what was + the `#+title'). + + Because of how it is meant to work, this dynamic block only works with + Org files. + + In its simplest form, this dynamic block looks like this, with + `:regexp' as the only mandatory parameter: + + ┌──── + │ #+BEGIN: denote-files-as-headings :regexp "YOUR REGEXP HERE" + │ + │ #+END: + └──── + + + Though when you use the command + `denote-org-dblock-insert-files-as-headings' you get all the + parameters included: + + ┌──── + │ #+BEGIN: denote-files-as-headings :regexp "YOUR REGEXP HERE" :not-regexp nil :excluded-dirs-regexp nil :sort-by-component title :reverse-sort nil :add-links t + │ + │ #+END: + └──── + + + • The `:regexp' parameter is mandatory. Its value is a string, + representing a regular expression to match Denote file names. Its + value may also be an `rx' expression instead of a string, as noted + in the previous section ([Org dynamic blocks to insert links or + backlinks]). Note that you do not need to write an actual regular + expression to get meaningful results: even something like `_journal' + will work to include all files that have a `journal' keyword. + + • The `:not-regexp' parameter is optional. It is a regular expression + that applies after `:regexp' to filter out the matching files. + + • The `:excluded-dirs-regexp' is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the `denote-excluded-directories-regexp' user option (which + is part of the main `denote' package)). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of `:excluded-dirs-regexp' is nil (the default), the value of + `denote-excluded-directories-regexp' is used (which is also nil by + default, meaning that all directories are included). When the value + of `excluded-dirs-regexp' is `t' or some other symbol, then the + `denote-excluded-directories-regexp' is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + + • The `:sort-by-component' parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among `title', `keywords', `signature', + `identifier'. When using the command + `denote-org-dblock-insert-files', this parameter is automatically + inserted together with the (`:regexp' parameter) and the user is + prompted for a file name component. + + • The `:reverse-sort' parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter `:sort-by-component', though it also combines with + it. + + • The `:add-links' parameter is optional. When it is set to a `t' + value, all the top-level headings (those that were the `#+title' of + each file) are generated as links, pointing to the original file. + This has the same meaning as with the `denote-link' command and + related facilities (those are explained at length in the Denote + manual). + + • An optional `:block-name' parameter can be specified with a string + value to add a `#+name' to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote’s + purview). + + +[Org dynamic block to insert file contents] See section 3.4 + +[Org dynamic blocks to insert links or backlinks] See section 3.1 + + +4 Create a note from the current Org subtree +════════════════════════════════════════════ + + In Org parlance, an entry with all its subheadings and other contents + is a “subtree”. Denote can operate on the subtree to extract it from + the current file and create a new file out of it. One such workflow is + to collect thoughts in a single document and produce longer standalone + notes out of them upon review. + + The command `denote-org-extract-org-subtree' is used for this + purpose. It creates a new Denote note using the current Org subtree. + In doing so, it removes the subtree from its current file and moves + its contents into a new file. This command is part of the optional + `denote-org.el' extension, which is part of the `denote' package. It + is loaded automatically as soon as one of its commands is invoked. + + The text of the subtree’s heading becomes the `#+title' of the new + note. Everything else is inserted as-is. + + If the heading has any tags, they are used as the keywords of the new + note. If the Org file has any `#+filetags' they are taken as well + (Org’s `#+filetags' are inherited by the headings). If none of these + are true and the user option `denote-prompts' includes an entry for + keywords, then `denote-org-extract-org-subtree' prompts for + keywords. Else the new note has no keywords. + + If the heading has a `PROPERTIES' drawer, it is retained for further + review. + + If the heading’s `PROPERTIES' drawer includes a `DATE' or `CREATED' + property, or there exists a `CLOSED' statement with a timestamp value, + use that to derive the date (or date and time) of the new note (if + there is only a date, the time is taken as 00:00). If more than one of + these is present, the order of preference is `DATE', then `CREATED', + then `CLOSED'. If none of these is present, the current time is used. + If the `denote-prompts' includes an entry for a date, then the command + prompts for a date at this stage (also see + `denote-date-prompt-use-org-read-date'). + + For the rest, it consults the value of the user option + `denote-prompts' in the following scenaria: + + • To optionally prompt for a subdirectory, otherwise it produces the + new note in the `denote-directory'. + • To optionally prompt for a file signature, otherwise to not use any. + + The new note is an Org file regardless of the user option + `denote-file-type'. + + +5 Insert link to an Org file with a further pointer to a heading +════════════════════════════════════════════════════════════════ + + As part of the optional `denote-org.el' extension, the command + `denote-org-link-to-heading' prompts for a link to an Org file and + then asks for a heading therein, using minibuffer completion. Once the + user provides input at the two prompts, the command inserts a link at + point which has the following pattern: + `[[denote:IDENTIFIER::#ORG-HEADING-CUSTOM-ID]][Description::Heading + text]]'. + + Because only Org files can have links to individual headings, the + command `denote-org-link-to-heading' prompts only for Org files + (i.e. files which include the `.org' extension). Remember that Denote + works with many file types (read the Denote manual’s section about the + file-naming scheme). + + This feature is similar to the concept of the user option + `denote-org-store-link-to-heading' (which is part of the main `denote' + package). It is, however, interactive and differs in the + directionality of the action. With that user option, the command + `org-store-link' will generate a `CUSTOM_ID' for the current heading + (or capture the value of one as-is), giving the user the option to + then call `org-insert-link' wherever they see fit. By contrast, the + command `denote-org-link-to-heading' prompts for a file, then a + heading, and inserts the link at point. + + Just as with files, it is possible to show backlinks for the given + heading ([Backlinks for Org headings]). + + +[Backlinks for Org headings] See section 5.1 + +5.1 Backlinks for Org headings +────────────────────────────── + + The optional `denote-org.el' can generate Denote links to individual + headings ([Insert link to an Org file with a further pointer to a + heading]). It is then possible to produce a corresponding backlinks + buffer with the command `denote-org-backlinks-for-heading'. The + resulting buffer behaves the same way as the standard backlinks buffer + we provide (read the Denote manual’s section about the backlinks + buffer). An Org dynamic block with backlinks to the current heading + is also an option ([Org dynamic blocks to insert links or backlinks]). + + +[Insert link to an Org file with a further pointer to a heading] See +section 5 + +[Org dynamic blocks to insert links or backlinks] See section 3.1 + + +6 Convert `denote:' links to `file:' links in Org and vice versa +════════════════════════════════════════════════════════════════ + + Sometimes the user needs to translate all `denote:' link types to + their `file:' equivalent. This may be because some other tool does not + recognise `denote:' links (or other custom links types—which are a + standard feature of Org, by the way). The user thus needs to (i) + either make a copy of their Denote note or edit the existing one, and + (ii) convert all links to the generic `file:' link type that + external/other programs understand. + + The optional extension `denote-org.el' contains two commands that are + relevant for this use-case: + + Convert `denote:' links to `file:' links + The command `denote-org-convert-links-to-file-type' goes through + the buffer to find all `denote:' links. It gets the identifier + of the link and resolves it to the actual file system path. It + then replaces the match so that the link is written with the + `file:' type and then the file system path. The optional search + terms and/or link description are preserved ([Insert link to an + Org file with a further pointer to a heading]). + + Convert `file:' links to `denote:' links + The command `denote-org-convert-links-to-denote-type' behaves + like the one above. The difference is that it finds the file + system path and converts it into its identifier. + + +[Insert link to an Org file with a further pointer to a heading] See +section 5 + + +7 Installation +══════════════ + + + + +7.1 GNU ELPA package +──────────────────── + + The package is available as `denote-org'. Simply do: + + ┌──── + │ M-x package-refresh-contents + │ M-x package-install + └──── + + + And search for it. + + GNU ELPA provides the latest stable release. Those who prefer to + follow the development process in order to report bugs or suggest + changes, can use the version of the package from the GNU-devel ELPA + archive. Read: + . + + +7.2 Manual installation +─────────────────────── + + Assuming your Emacs files are found in `~/.emacs.d/', execute the + following commands in a shell prompt: + + ┌──── + │ cd ~/.emacs.d + │ + │ # Create a directory for manually-installed packages + │ mkdir manual-packages + │ + │ # Go to the new directory + │ cd manual-packages + │ + │ # Clone this repo, naming it "denote-org" + │ git clone https://github.com/protesilaos/denote-org denote-org + └──── + + Finally, in your `init.el' (or equivalent) evaluate this: + + ┌──── + │ ;; Make Elisp files in that directory available to the user. + │ (add-to-list 'load-path "~/.emacs.d/manual-packages/denote-org") + └──── + + Everything is in place to set up the package. + + +8 Sample configuration +══════════════════════ + + ┌──── + │ (use-package denote-org + │ :ensure t + │ :commands + │ ;; I list the commands here so that you can discover them more + │ ;; easily. You might want to bind the most frequently used ones to + │ ;; the `org-mode-map'. + │ ( denote-org-link-to-heading + │ denote-org-backlinks-for-heading + │ + │ denote-org-extract-org-subtree + │ + │ denote-org-convert-links-to-file-type + │ denote-org-convert-links-to-denote-type + │ + │ denote-org-dblock-insert-files + │ denote-org-dblock-insert-links + │ denote-org-dblock-insert-backlinks + │ denote-org-dblock-insert-missing-links + │ denote-org-dblock-insert-files-as-headings)) + └──── + + +9 Acknowledgements +══════════════════ + + Denote Org is meant to be a collective effort. Every bit of help + matters. + + Author/maintainer + Protesilaos Stavrou. + + +10 GNU Free Documentation License +═════════════════════════════════ + + +11 Indices +══════════ + +11.1 Function index +─────────────────── + + +11.2 Variable index +─────────────────── + + +11.3 Concept index +────────────────── blob - /dev/null blob + 3bbde61a60b27bcd655963f17f77f0eb2a675032 (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/README.md @@ -0,0 +1,17 @@ +# denote-org: Extras to integrate Org mode with Denote (like Org dynamic blocks) + +The `denote-org` package contains extra features that better integrate +Denote with Org mode. These used to be available as part of the main +`denote` package in a file called `denote-org-extras.el`, but now live +in this standalone package to main things easier to maintain and +understand. + +With `denote-org`, users have Org-specific extensions such as dynamic +blocks, links to headings, and splitting an Org subtree into its own +standalone file. This package's official manual covers the +technicalities. + ++ Package name (GNU ELPA): `denote-org` ++ Official manual: ++ Git repository: ++ Backronym: Denote... Ordinarily Restricts Gyrations. blob - /dev/null blob + c43f9b8cc549eb3ebefb559193fd172721c4fae0 (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/README.org @@ -0,0 +1,1152 @@ +#+title: denote-org: Extensions to better integrate Org with Denote +#+author: Protesilaos Stavrou +#+email: info@protesilaos.com +#+language: en +#+options: ':t toc:nil author:t email:t num:t +#+startup: content +#+macro: stable-version 0.1.0 +#+macro: release-date 2025-04-15 +#+macro: development-version 0.2.0-dev +#+export_file_name: denote-org.texi +#+texinfo_filename: denote-org.info +#+texinfo_dir_category: Emacs misc features +#+texinfo_dir_title: Denote Org: (denote-org) +#+texinfo_dir_desc: Extensions to better integrate Org with Denote +#+texinfo_header: @set MAINTAINERSITE @uref{https://protesilaos.com,maintainer webpage} +#+texinfo_header: @set MAINTAINER Protesilaos Stavrou +#+texinfo_header: @set MAINTAINEREMAIL @email{info@protesilaos.com} +#+texinfo_header: @set MAINTAINERCONTACT @uref{mailto:info@protesilaos.com,contact the maintainer} + +#+texinfo: @insertcopying + +This manual, written by Protesilaos Stavrou, describes the customization +options for the Emacs package called ~denote~ (or =denote.el=), and +provides every other piece of information pertinent to it. + +The documentation furnished herein corresponds to stable version +{{{stable-version}}}, released on {{{release-date}}}. Any reference to +a newer feature which does not yet form part of the latest tagged +commit, is explicitly marked as such. + +Current development target is {{{development-version}}}. + ++ Package name (GNU ELPA): ~denote-org~ ++ Official manual: ++ Git repository: ++ Backronym: Denote... Ordinarily Restricts Gyrations. + +If you are viewing the README.org version of this file, please note that +the GNU ELPA machinery automatically generates an Info manual out of it. + +#+toc: headlines 8 insert TOC here, with eight headline levels + +* COPYING +:PROPERTIES: +:COPYING: t +:CUSTOM_ID: h:copying +:END: + +Copyright (C) 2022-2025 Free Software Foundation, Inc. + +#+begin_quote +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and +with the Back-Cover Texts as in (a) below. A copy of the license is +included in the section entitled “GNU Free Documentation License.” + +(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and +modify this GNU manual.” +#+end_quote + +* Overview +:PROPERTIES: +:CUSTOM_ID: h:6157c29a-31e6-4ba7-b114-57f94a21d460 +:END: + +The ~denote-org~ package contains extra features that better integrate +Denote with Org mode. These used to be available as part of the main +~denote~ package in a file called =denote-org-extras.el=, but now live +in this standalone package to main things easier to maintain and +understand. + +With ~denote-org~, users have Org-specific extensions such as dynamic +blocks, links to headings, and splitting an Org subtree into its own +standalone file. The following sections cover the technicalities. + +* Use Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:8b542c50-dcc9-4bca-8037-a36599b22779 +:END: + +Denote can optionally integrate with Org mode's "dynamic blocks" +facility. This means that it can use special blocks that are evaluated +with =C-c C-x C-u= (~org-dblock-update~) to generate their contents. +The following subsections describe the types of Org dynamic blocks +provided by Denote. + +- [[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links or backlinks]] +- [[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic block to insert file contents]] + +A dynamic block gets its contents by evaluating a function that +corresponds to the type of block. The block type and its parameters +are stated in the opening =#+BEGIN= line. Typing =C-c C-x C-u= +(~org-dblock-update~) with point on that line runs (or re-runs) the +associated function with the given parameters and populates the +block's contents accordingly. + +Dynamic blocks are particularly useful for metanote entries that +reflect on the status of earlier notes (read the Denote manual's +section about writing metanotes). + +The Org manual describes the technicalities of Dynamic Blocks. +Evaluate: + +#+begin_src emacs-lisp +(info "(org) Dynamic Blocks") +#+end_src + +** Org dynamic blocks to insert links +:PROPERTIES: +:CUSTOM_ID: h:50160fae-6515-4d7d-9737-995ad925e64b +:END: + +#+findex: denote-org-dblock-insert-links +The =denote-links= block can be inserted at point with the command +~denote-org-dblock-insert-links~ or by manually including the +following in an Org file: + +: #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :not-regexp :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :include-date nil +: +: #+END: + +All the parameters except for =:regexp= are optional. + +The =denote-links= block is also registered as an option for the +command ~org-dynamic-block-insert-dblock~. + +Type =C-c C-x C-u= (~org-dblock-update~) with point on the =#+BEGIN= +line to update the block. + +- The =:regexp= parameter is mandatory. Its value is a string and its + behaviour is the same as that of the standard ~denote-add-links~ + command (part of the main ~denote~ package). Concretely, it produces + a typographic list of links to files matching the giving regular + expression. The value of the =:regexp= parameter may also be of the + form read by the ~rx~ macro (Lisp notation instead of a string), as + explained in the Emacs Lisp Reference Manual (evaluate this code to + read the documentation: =(info "(elisp) Rx Notation")=). Note that + you do not need to write an actual regular expression to get + meaningful results: even something like =_journal= will work to + include all files that have a =journal= keyword. + +- The =:not-regexp= parameter is optional. It is a regular expression + that applies after =:regexp= to filter out the matching files. + +- The =:excluded-dirs-regexp= is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the ~denote-excluded-directories-regexp~ user option (which + is part of the main ~denote~ package). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of =:excluded-dirs-regexp= is nil (the default), the value of + ~denote-excluded-directories-regexp~ is used (which is also nil by + default, meaning that all directories are included). When the value + of =excluded-dirs-regexp= is ~t~ or some other symbol, then the + ~denote-excluded-directories-regexp~ is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + +- The =:sort-by-component= parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among =title=, =keywords=, =signature=, =identifier=. + When using the command ~denote-org-dblock-insert-files~, this + parameter is automatically inserted together with the (=:regexp= + parameter) and the user is prompted for a file name component. + +- The =:reverse-sort= parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter =:sort-by-component=, though it also combines with + it. + +- The =:id-only= parameter is optional. It accepts a ~t~ value, in + which case links are inserted without a description text but only + with the identifier of the given file. This has the same meaning as + with the ~denote-link~ command and related facilities (read the + Denote manual's section about linking to other files in the + ~denote-directory~). + +- The =:include-date= parameter controls whether to display the date + of the file name after the title. This is done when its value is + ~t~. By default (a nil value), no date is shown. + +- An optional =:block-name= parameter can be specified with a string + value to add a =#+name= to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote's + purview). + +In some workflows, users may want to have a separate block to see what +other links they are missing since they last updated the dynamic +block. We cover that case as well ([[#h:1a81e255-0510-4ee0-bc3a-374de048ef46][The Org dynamic block to insert missing links only]]). + +** The Org dynamic block to insert missing links only +:PROPERTIES: +:CUSTOM_ID: h:1a81e255-0510-4ee0-bc3a-374de048ef46 +:END: + +#+findex: denote-org-dblock-insert-missing-links +The =denote-missing-links= block is available with the command +~denote-org-dblock-insert-missing-links~. It is like the +aforementioned =denote-links= block, except it only lists links to +files that are not present in the current buffer ([[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links]]). +The parameters are otherwise the same and are all optional except for +=:regexp=: + +: #+BEGIN: denote-missing-links :regexp "YOUR REGEXP HERE" :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :include-date nil +: +: #+END: + +The =denote-missing-links= block is also registered as an option for the +command ~org-dynamic-block-insert-dblock~. + +Remember to type =C-c C-x C-u= (~org-dblock-update~) with point on the +=#+BEGIN= line to update the block. + +** The Org dynamic block to insert backlinks +:PROPERTIES: +:CUSTOM_ID: h:f9a97859-1deb-47dd-bdae-52f8b424ff46 +:END: + +#+findex: denote-org-dblock-insert-backlinks +Apart from links to files matching a regular expression, we can also +produce a list of backlinks to the current file. The dynamic block can +be inserted at point with the command ~denote-org-dblock-insert-backlinks~ +or by manually writing this in an Org file: + +: #+BEGIN: denote-backlinks :excluded-dirs-regexp nil :sort-by-component nil :reverse-sort nil :id-only nil :this-heading-only nil :include-date nil +: +: #+END: + +The =denote-backlinks= block is also registered as an option for the +command ~org-dynamic-block-insert-dblock~. + +Remember to type =C-c C-x C-u= (~org-dblock-update~) with point on the +=#+BEGIN= line to update the block. + +The parameters recognised by this dynamic block are almost the same as +that for inserting links ([[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links]]). They +are all optional in this case and there is no parameter expecting a +regular expression for matching files to link to. + +Additionally, the ~denote-backlinks~ block also recognises the +=:this-heading-only= parameter. It determines if the backlinks are +about the file or the heading under which the dynamic block is inserted +([[#h:604bf92a-908a-485c-98b8-37ccae559afd][Backlinks for Org headings]]). When this parameter is omitted or nil +(the default), then the backlinks are about the whole file, but if +this parameter has a ~t~ value then the backlinks are specifically for +the heading ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a heading]]). + +** Org dynamic block to insert file contents +:PROPERTIES: +:CUSTOM_ID: h:f15fa143-5036-416f-9bff-1bcabbb03456 +:END: + +Denote can optionally use Org's dynamic blocks facility to produce a +section that lists entire file contents ([[#h:8b542c50-dcc9-4bca-8037-a36599b22779][Use Org dynamic blocks]]). +This works by instructing Org to match a regular expression of Denote +files, the same way we do with Denote links (read the Denote manual's +section about inserting links that match a regular expression). + +This is useful to, for example, compile a dynamically concatenated +list of scattered thoughts on a given topic, like =^2023.*_emacs= for +a long entry that incorporates all the notes written in 2023 with the +keyword =emacs=. + +#+findex: denote-org-dblock-insert-files +To produce such a block, call the command ~denote-org-dblock-insert-files~ +or manually write the following block in an Org file and then type + =C-c C-x C-u= (~org-dblock-update~) on the =#+BEGIN= line to run it +(do it again to recalculate the block): + +: #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :not-regexp nil :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil +: +: #+END: + +All parameters are optional except for =:regexp=. + +The =denote-files= block is also registered as an option for the +command ~org-dynamic-block-insert-dblock~. + +Remember to type =C-c C-x C-u= (~org-dblock-update~) with point on the +=#+BEGIN= line to update the block. + +To fully control the output, include these additional optional +parameters, which are described further below: + +- The =:regexp= parameter is mandatory. Its value is a string, + representing a regular expression to match Denote file names. Its + value may also be an ~rx~ expression instead of a string, as noted + in the previous section ([[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links or backlinks]]). + Note that you do not need to write an actual regular expression to + get meaningful results: even something like =_journal= will work to + include all files that have a =journal= keyword. + +- The =:not-regexp= parameter is optional. It is a regular expression + that applies after =:regexp= to filter out the matching files. + +- The =:excluded-dirs-regexp= is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the ~denote-excluded-directories-regexp~ user option (which + is part of the main ~denote~ package). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of =:excluded-dirs-regexp= is nil (the default), the value of + ~denote-excluded-directories-regexp~ is used (which is also nil by + default, meaning that all directories are included). When the value + of =excluded-dirs-regexp= is ~t~ or some other symbol, then the + ~denote-excluded-directories-regexp~ is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + +- The =:sort-by-component= parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among =title=, =keywords=, =signature=, =identifier=. + When using the command ~denote-org-dblock-insert-files~, this + parameter is automatically inserted together with the (=:regexp= + parameter) and the user is prompted for a file name component. + +- The =:reverse-sort= parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter =:sort-by-component=, though it also combines with + it. + +#+vindex: denote-org-dblock-file-contents-separator +- The =:file-separator= parameter is optional. If it is omitted, then + Denote will use no separator between the files it inserts. If the + value is ~t~ the ~denote-org-dblock-file-contents-separator~ is + applied at the end of each file: it introduces some empty lines and + a horizontal rule between them to visually distinguish individual + files. If the =:file-separator= value is a string, it is used as the + file separator (e.g. use ="\n"= to insert just one empty new line). + +- The =:no-front-matter= parameter is optional. When set to a ~t~ + value, Denote tries to remove front matter from the files it is + inserting in the dynamic block. The technique used to perform this + operation is by removing all lines from the top of the file until + the first empty line. This works with the default front matter that + Denote adds, but is not 100% reliable with all sorts of user-level + modifications and edits to the file. When the =:no-front-matter= is + set to a natural number, Denote will omit that many lines from the + top of the file. + +- The =:add-links= parameter is optional. When it is set to a ~t~ + value, all files are inserted as a typographic list and are indented + accordingly. The first line in each list item is a link to the file + whose contents are inserted in the following lines. When the value + is =id-only=, then links are inserted without a description text but + only with the identifier of the given file. This has the same + meaning as with the ~denote-link~ command and related facilities + (those are explained at length in the Denote manual). Remember that + Org can fold the items in a typographic list the same way it does + with headings. So even long files can be presented in this format + without much trouble. + +- An optional =:block-name= parameter can be specified with a string + value to add a =#+name= to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote's + purview). + +** Org dynamic block to insert Org files as headings +:PROPERTIES: +:CUSTOM_ID: h:d6254a12-b762-4096-a5de-66a0d423e204 +:END: + +[ IMPORTANT NOTE: This dynamic block only works with Org files, + because it has to assume the Org notation in order to insert each + file's contents as its own heading. ] + +#+findex: denote-org-dblock-insert-files-as-headings +As a variation of the previously covered block that inserts file +contents, we have the ~denote-org-dblock-insert-files-as-headings~ +command ([[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic block to insert file contents]]). It Turn the +=#+title= of each file into a top-level heading. Then it increments +all original headings in the file by one, so that they become +subheadings of what once was the =#+title=. Similarly, the +=#+filetags= of each file as tags for the top-level heading +(what was the =#+title=). + +Because of how it is meant to work, this dynamic block only works with +Org files. + +In its simplest form, this dynamic block looks like this, with +=:regexp= as the only mandatory parameter: + +: #+BEGIN: denote-files-as-headings :regexp "YOUR REGEXP HERE" +: +: #+END: + +Though when you use the command ~denote-org-dblock-insert-files-as-headings~ +you get all the parameters included: + +: #+BEGIN: denote-files-as-headings :regexp "YOUR REGEXP HERE" :not-regexp nil :excluded-dirs-regexp nil :sort-by-component title :reverse-sort nil :add-links t +: +: #+END: + +- The =:regexp= parameter is mandatory. Its value is a string, + representing a regular expression to match Denote file names. Its + value may also be an ~rx~ expression instead of a string, as noted + in the previous section ([[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links or backlinks]]). + Note that you do not need to write an actual regular expression to + get meaningful results: even something like =_journal= will work to + include all files that have a =journal= keyword. + +- The =:not-regexp= parameter is optional. It is a regular expression + that applies after =:regexp= to filter out the matching files. + +- The =:excluded-dirs-regexp= is a string that contains a word or + regular expression that matches against directory files names + to-be-excluded from the results. This has the same meaning as + setting the ~denote-excluded-directories-regexp~ user option (which + is part of the main ~denote~ package)). The user option has a global + effect, which is overridden locally in the dynamic block. When the + value of =:excluded-dirs-regexp= is nil (the default), the value of + ~denote-excluded-directories-regexp~ is used (which is also nil by + default, meaning that all directories are included). When the value + of =excluded-dirs-regexp= is ~t~ or some other symbol, then the + ~denote-excluded-directories-regexp~ is ignored altogether. This is + useful in the scenario where the user option is set to exclude some + directories but the dynamic blocks wants to lift that restriction. + +- The =:sort-by-component= parameter is optional. It sorts the files + by the given Denote file name component. The value it accepts is an + unquoted symbol among =title=, =keywords=, =signature=, =identifier=. + When using the command ~denote-org-dblock-insert-files~, this + parameter is automatically inserted together with the (=:regexp= + parameter) and the user is prompted for a file name component. + +- The =:reverse-sort= parameter is optional. It reverses the order in + which files appear in. This is meaningful even without the presence + of the parameter =:sort-by-component=, though it also combines with + it. + +- The =:add-links= parameter is optional. When it is set to a ~t~ + value, all the top-level headings (those that were the =#+title= of + each file) are generated as links, pointing to the original file. + This has the same meaning as with the ~denote-link~ command and + related facilities (those are explained at length in the Denote manual). + +- An optional =:block-name= parameter can be specified with a string + value to add a =#+name= to the results. This is useful for further + processing using Org facilities (a feature that is outside Denote's + purview). + +* Create a note from the current Org subtree +:PROPERTIES: +:CUSTOM_ID: h:d0c7cb79-21e5-4176-a6af-f4f68578c8dd +:END: + +In Org parlance, an entry with all its subheadings and other contents +is a "subtree". Denote can operate on the subtree to extract it from +the current file and create a new file out of it. One such workflow is +to collect thoughts in a single document and produce longer standalone +notes out of them upon review. + +#+findex: denote-org-extract-org-subtree +The command ~denote-org-extract-org-subtree~ is used for this +purpose. It creates a new Denote note using the current Org subtree. +In doing so, it removes the subtree from its current file and moves +its contents into a new file. This command is part of the optional +=denote-org.el= extension, which is part of the ~denote~ +package. It is loaded automatically as soon as one of its commands is +invoked. + +The text of the subtree's heading becomes the =#+title= of the new +note. Everything else is inserted as-is. + +If the heading has any tags, they are used as the keywords of the new +note. If the Org file has any =#+filetags= they are taken as well +(Org's =#+filetags= are inherited by the headings). If none of these +are true and the user option ~denote-prompts~ includes an entry for +keywords, then ~denote-org-extract-org-subtree~ prompts for +keywords. Else the new note has no keywords. + +If the heading has a =PROPERTIES= drawer, it is retained for further +review. + +If the heading's =PROPERTIES= drawer includes a =DATE= or =CREATED= +property, or there exists a =CLOSED= statement with a timestamp value, +use that to derive the date (or date and time) of the new note (if +there is only a date, the time is taken as 00:00). If more than one of +these is present, the order of preference is =DATE=, then =CREATED=, +then =CLOSED=. If none of these is present, the current time is used. +If the ~denote-prompts~ includes an entry for a date, then the command +prompts for a date at this stage (also see ~denote-date-prompt-use-org-read-date~). + +For the rest, it consults the value of the user option +~denote-prompts~ in the following scenaria: + +- To optionally prompt for a subdirectory, otherwise it produces the + new note in the ~denote-directory~. +- To optionally prompt for a file signature, otherwise to not use any. + +The new note is an Org file regardless of the user option +~denote-file-type~. + +* Insert link to an Org file with a further pointer to a heading +:PROPERTIES: +:CUSTOM_ID: h:fc1ad245-ec08-41be-8d1e-7153d99daf02 +:END: + +#+findex: denote-org-link-to-heading +As part of the optional =denote-org.el= extension, the command +~denote-org-link-to-heading~ prompts for a link to an Org file +and then asks for a heading therein, using minibuffer completion. Once +the user provides input at the two prompts, the command inserts a link +at point which has the following pattern: =[[denote:IDENTIFIER::#ORG-HEADING-CUSTOM-ID]][Description::Heading text]]=. + +Because only Org files can have links to individual headings, the +command ~denote-org-link-to-heading~ prompts only for Org files +(i.e. files which include the =.org= extension). Remember that Denote +works with many file types (read the Denote manual's section about the +file-naming scheme). + +This feature is similar to the concept of the user option +~denote-org-store-link-to-heading~ (which is part of the main ~denote~ +package). It is, however, interactive and differs in the +directionality of the action. With that user option, the command +~org-store-link~ will generate a =CUSTOM_ID= for the current heading +(or capture the value of one as-is), giving the user the option to +then call ~org-insert-link~ wherever they see fit. By contrast, the +command ~denote-org-link-to-heading~ prompts for a file, then a +heading, and inserts the link at point. + +Just as with files, it is possible to show backlinks for the given +heading ([[#h:604bf92a-908a-485c-98b8-37ccae559afd][Backlinks for Org headings]]). + +** Backlinks for Org headings +:PROPERTIES: +:CUSTOM_ID: h:604bf92a-908a-485c-98b8-37ccae559afd +:END: + +The optional =denote-org.el= can generate Denote links to +individual headings ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a heading]]). +It is then possible to produce a corresponding backlinks buffer with +the command ~denote-org-backlinks-for-heading~. The resulting +buffer behaves the same way as the standard backlinks buffer we +provide (read the Denote manual's section about the backlinks buffer). +An Org dynamic block with backlinks to the current heading is also an +option ([[#h:50160fae-6515-4d7d-9737-995ad925e64b][Org dynamic blocks to insert links or backlinks]]). + +* Convert =denote:= links to =file:= links in Org and vice versa +:PROPERTIES: +:CUSTOM_ID: h:ed220cac-7dcb-4bb7-9243-1bb85e452e5f +:END: + +Sometimes the user needs to translate all =denote:= link types to +their =file:= equivalent. This may be because some other tool does not +recognise =denote:= links (or other custom links types---which are a +standard feature of Org, by the way). The user thus needs to (i) +either make a copy of their Denote note or edit the existing one, and +(ii) convert all links to the generic =file:= link type that +external/other programs understand. + +The optional extension =denote-org.el= contains two commands +that are relevant for this use-case: + +#+findex: denote-org-convert-links-to-file-type ++ Convert =denote:= links to =file:= links :: The command + ~denote-org-convert-links-to-file-type~ goes through the + buffer to find all =denote:= links. It gets the identifier of the + link and resolves it to the actual file system path. It then + replaces the match so that the link is written with the =file:= type + and then the file system path. The optional search terms and/or link + description are preserved ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a heading]]). + +#+findex: denote-org-convert-links-to-denote-type ++ Convert =file:= links to =denote:= links :: The command + ~denote-org-convert-links-to-denote-type~ behaves like the + one above. The difference is that it finds the file system path and + converts it into its identifier. + +* Installation +:PROPERTIES: +:CUSTOM_ID: h:installation +:END: +#+cindex: Installation instructions + +** GNU ELPA package +:PROPERTIES: +:CUSTOM_ID: h:gnu-elpa-package +:END: + +The package is available as =denote-org=. Simply do: + +: M-x package-refresh-contents +: M-x package-install + +And search for it. + +GNU ELPA provides the latest stable release. Those who prefer to follow +the development process in order to report bugs or suggest changes, can +use the version of the package from the GNU-devel ELPA archive. Read: +https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/. + +** Manual installation +:PROPERTIES: +:CUSTOM_ID: h:manual-installation +:END: + +Assuming your Emacs files are found in =~/.emacs.d/=, execute the +following commands in a shell prompt: + +#+begin_src sh +cd ~/.emacs.d + +# Create a directory for manually-installed packages +mkdir manual-packages + +# Go to the new directory +cd manual-packages + +# Clone this repo, naming it "denote-org" +git clone https://github.com/protesilaos/denote-org denote-org +#+end_src + +Finally, in your =init.el= (or equivalent) evaluate this: + +#+begin_src emacs-lisp +;; Make Elisp files in that directory available to the user. +(add-to-list 'load-path "~/.emacs.d/manual-packages/denote-org") +#+end_src + +Everything is in place to set up the package. + +* Sample configuration +:PROPERTIES: +:CUSTOM_ID: h:sample-configuration +:END: +#+cindex: Package configuration + +#+begin_src emacs-lisp +(use-package denote-org + :ensure t + :commands + ;; I list the commands here so that you can discover them more + ;; easily. You might want to bind the most frequently used ones to + ;; the `org-mode-map'. + ( denote-org-link-to-heading + denote-org-backlinks-for-heading + + denote-org-extract-org-subtree + + denote-org-convert-links-to-file-type + denote-org-convert-links-to-denote-type + + denote-org-dblock-insert-files + denote-org-dblock-insert-links + denote-org-dblock-insert-backlinks + denote-org-dblock-insert-missing-links + denote-org-dblock-insert-files-as-headings)) +#+end_src + +* Acknowledgements +:PROPERTIES: +:CUSTOM_ID: h:acknowledgements +:END: +#+cindex: Contributors + +Denote Org is meant to be a collective effort. Every bit of help matters. + ++ Author/maintainer :: Protesilaos Stavrou. + +* GNU Free Documentation License +:PROPERTIES: +:APPENDIX: t +:CUSTOM_ID: h:gnu-free-documentation-license +:END: + +#+texinfo: @include doclicense.texi + +#+begin_export html +
+
+                GNU Free Documentation License
+                 Version 1.3, 3 November 2008
+
+
+ Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
+     
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense.  It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does.  But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book.  We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License.  Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein.  The "Document", below,
+refers to any such manual or work.  Any member of the public is a
+licensee, and is addressed as "you".  You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject.  (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.)  The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License.  If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant.  The Document may contain zero
+Invariant Sections.  If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License.  A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters.  A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text.  A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification.  Examples of
+transparent image formats include PNG, XCF and JPG.  Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page.  For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+The "publisher" means any person or entity that distributes copies of
+the Document to the public.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language.  (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".)  To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document.  These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no
+other conditions whatsoever to those of this License.  You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute.  However, you may accept
+compensation in exchange for copies.  If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover.  Both covers must also clearly and legibly identify
+you as the publisher of these copies.  The front cover must present
+the full title with all words of the title equally prominent and
+visible.  You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to
+give them a chance to provide you with an updated version of the
+Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it.  In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+   from that of the Document, and from those of previous versions
+   (which should, if there were any, be listed in the History section
+   of the Document).  You may use the same title as a previous version
+   if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+   responsible for authorship of the modifications in the Modified
+   Version, together with at least five of the principal authors of the
+   Document (all of its principal authors, if it has fewer than five),
+   unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+   Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+   adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+   giving the public permission to use the Modified Version under the
+   terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+   and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+   to it an item stating at least the title, year, new authors, and
+   publisher of the Modified Version as given on the Title Page.  If
+   there is no section Entitled "History" in the Document, create one
+   stating the title, year, authors, and publisher of the Document as
+   given on its Title Page, then add an item describing the Modified
+   Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+   public access to a Transparent copy of the Document, and likewise
+   the network locations given in the Document for previous versions
+   it was based on.  These may be placed in the "History" section.
+   You may omit a network location for a work that was published at
+   least four years before the Document itself, or if the original
+   publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+   Preserve the Title of the section, and preserve in the section all
+   the substance and tone of each of the contributor acknowledgements
+   and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+   unaltered in their text and in their titles.  Section numbers
+   or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements".  Such a section
+   may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+   or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant.  To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version.  Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity.  If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy.  If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications".  You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other
+documents released under this License, and replace the individual
+copies of this License in the various documents with a single copy
+that is included in the collection, provided that you follow the rules
+of this License for verbatim copying of each of the documents in all
+other respects.
+
+You may extract a single document from such a collection, and
+distribute it individually under this License, provided you insert a
+copy of this License into the extracted document, and follow this
+License in all other respects regarding verbatim copying of that
+document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections.  You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers.  In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense, or distribute it is void, and
+will automatically terminate your rights under this License.
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, receipt of a copy of some or all of the same material does
+not give you any rights to use it.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions of the
+GNU Free Documentation License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in
+detail to address new problems or concerns.  See
+https://www.gnu.org/licenses/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation.  If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation.  If the Document
+specifies that a proxy can decide which future versions of this
+License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the
+Document.
+
+11. RELICENSING
+
+"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
+World Wide Web server that publishes copyrightable works and also
+provides prominent facilities for anybody to edit those works.  A
+public wiki that anybody can edit is an example of such a server.  A
+"Massive Multiauthor Collaboration" (or "MMC") contained in the site
+means any set of copyrightable works thus published on the MMC site.
+
+"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
+license published by Creative Commons Corporation, a not-for-profit
+corporation with a principal place of business in San Francisco,
+California, as well as future copyleft versions of that license
+published by that same organization.
+
+"Incorporate" means to publish or republish a Document, in whole or in
+part, as part of another Document.
+
+An MMC is "eligible for relicensing" if it is licensed under this
+License, and if all works that were first published under this License
+somewhere other than this MMC, and subsequently incorporated in whole or
+in part into the MMC, (1) had no cover texts or invariant sections, and
+(2) were thus incorporated prior to November 1, 2008.
+
+The operator of an MMC Site may republish an MMC contained in the site
+under CC-BY-SA on the same site at any time before August 1, 2009,
+provided the MMC is eligible for relicensing.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+    Copyright (c)  YEAR  YOUR NAME.
+    Permission is granted to copy, distribute and/or modify this document
+    under the terms of the GNU Free Documentation License, Version 1.3
+    or any later version published by the Free Software Foundation;
+    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+    A copy of the license is included in the section entitled "GNU
+    Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+    with the Invariant Sections being LIST THEIR TITLES, with the
+    Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
+
+#+end_export + +#+html: blob - /dev/null blob + 6fa6116de64b21b9817bd575e6957eece875fd5b (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/denote-org-autoloads.el @@ -0,0 +1,164 @@ +;;; denote-org-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*- +;; Generated by the `loaddefs-generate' function. + +;; This file is part of GNU Emacs. + +;;; Code: + +(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path))) + + + +;;; Generated autoloads from denote-org.el + +(autoload 'denote-org-link-to-heading "denote-org" "\ +Link to file and then specify a heading to extend the link to. + +The resulting link has the following pattern: + +[[denote:IDENTIFIER::#ORG-HEADING-CUSTOM-ID]][Description::Heading text]]. + +Because only Org files can have links to individual headings, +limit the list of possible files to those which include the .org +file extension (remember that Denote works with many file types, +per the user option `denote-file-type'). + +The user option `denote-org-store-link-to-heading' +determined whether the `org-store-link' function can save a link +to the current heading. Such links look the same as those of +this command, though the functionality defined herein is +independent of it. + +To only link to a file, use the `denote-link' command. + +Also see `denote-org-backlinks-for-heading'." '(org-mode)) +(function-put 'denote-org-link-to-heading 'interactive-only 't) +(autoload 'denote-org-backlinks-for-heading "denote-org" "\ +Produce backlinks for the current heading. +This otherwise has the same behaviour as `denote-backlinks'---refer to +that for the details. + +Also see `denote-org-link-to-heading'." t) +(autoload 'denote-org-extract-org-subtree "denote-org" "\ +Create new Denote note using the current Org subtree as input. +Remove the subtree from its current file and move its contents into a +new Denote file (a subtree is a heading with all of its contents, +including subheadings). + +Take the text of the subtree's top level heading and use it as the title +of the new note. + +If the heading has any tags, use them as the keywords of the new note. +If the Org file has any #+filetags use them as well (Org's filetags are +inherited by the headings). If none of these are true and the user +option `denote-prompts' includes an entry for keywords, then prompt for +keywords. Else do not include any keywords. + +If the heading has a PROPERTIES drawer, retain it for further review. + +If the heading's PROPERTIES drawer includes a DATE or CREATED property, +or there exists a CLOSED statement with a timestamp value, use that to +derive the date (or date and time) of the new note (if there is only a +date, the time is taken as 00:00). If more than one of these is +present, the order of preference is DATE, then CREATED, then CLOSED. If +none of these is present, use the current time. If the `denote-prompts' +includes an entry for a date, then prompt for a date at this stage (also +see `denote-date-prompt-use-org-read-date'). + +For the rest, consult the value of the user option `denote-prompts' in +the following scenaria: + +- Optionally prompt for a subdirectory, otherwise produce the new note + in the variable `denote-directory'. + +- Optionally prompt for a file signature, otherwise do not use one. + +Make the new note an Org file regardless of the value of the user option +`denote-file-type'." '(org-mode)) +(autoload 'denote-org-convert-links-to-file-type "denote-org" "\ +Convert denote: links to file: links in the current Org buffer. +Ignore all other link types. Also ignore links that do not +resolve to a file in the variable `denote-directory'." '(org-mode)) +(autoload 'denote-org-convert-links-to-denote-type "denote-org" "\ +Convert file: links to denote: links in the current Org buffer. +Ignore all other link types. Also ignore file: links that do not +point to a file with a Denote file name." '(org-mode)) +(autoload 'denote-org-dblock-insert-links "denote-org" "\ +Create Org dynamic block to insert Denote links matching REGEXP. + +(fn REGEXP)" '(org-mode)) +(eval-after-load 'org '(progn (org-dynamic-block-define "denote-links" 'denote-org-dblock-insert-links))) +(autoload 'org-dblock-write:denote-links "denote-org" "\ +Function to update `denote-links' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block. + +(fn PARAMS)") +(autoload 'denote-org-dblock-insert-missing-links "denote-org" "\ +Create Org dynamic block to insert Denote links matching REGEXP. + +(fn REGEXP)" '(org-mode)) +(eval-after-load 'org '(progn (org-dynamic-block-define "denote-missing-links" 'denote-org-dblock-insert-missing-links))) +(autoload 'org-dblock-write:denote-missing-links "denote-org" "\ +Function to update `denote-links' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block. + +(fn PARAMS)") +(autoload 'denote-org-dblock-insert-backlinks "denote-org" "\ +Create Org dynamic block to insert Denote backlinks to current file." '(org-mode)) +(eval-after-load 'org '(progn (org-dynamic-block-define "denote-backlinks" 'denote-org-dblock-insert-backlinks))) +(autoload 'org-dblock-write:denote-backlinks "denote-org" "\ +Function to update `denote-backlinks' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block. + +(fn PARAMS)") +(autoload 'denote-org-dblock-insert-files "denote-org" "\ +Create Org dynamic block to insert Denote files matching REGEXP. +Sort the files according to SORT-BY-COMPONENT, which is a symbol +among `denote-sort-components'. + +(fn REGEXP SORT-BY-COMPONENT)" '(org-mode)) +(eval-after-load 'org '(progn (org-dynamic-block-define "denote-files" 'denote-org-dblock-insert-files))) +(autoload 'org-dblock-write:denote-files "denote-org" "\ +Function to update `denote-files' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block. + +(fn PARAMS)") +(autoload 'denote-org-dblock-insert-files-as-headings "denote-org" "\ +Create Org dynamic block to insert Denote Org files matching REGEXP. + +Turn the #+title of each file into a top-level heading. Then increment +all original headings in the file by one, so that they become +subheadings of what once was the #+title. + +Use the #+filetags of each file as tags for the top-level heading (what +was the #+title). + +Sort the files according to SORT-BY-COMPONENT, which is a symbol +among `denote-sort-components'. + +IMPORTANT NOTE: This dynamic block only works with Org files, because it +has to assume the Org notation in order to insert each file's contents +as its own heading. + +(fn REGEXP SORT-BY-COMPONENT)" '(org-mode)) +(eval-after-load 'org '(progn (org-dynamic-block-define "denote-files-as-headings" 'denote-org-dblock-insert-files-as-headings))) +(autoload 'org-dblock-write:denote-files-as-headings "denote-org" "\ +Function to update `denote-files' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block. + +(fn PARAMS)") +(register-definition-prefixes "denote-org" '("denote-org-")) + +;;; End of scraped data + +(provide 'denote-org-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; denote-org-autoloads.el ends here blob - /dev/null blob + 8e1f48d7ad74b033b24c3cbe7e83108ec334769f (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/denote-org-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from denote-org.el -*- mode: lisp-data; no-byte-compile: t -*- +(define-package "denote-org" "0.1.1" "Denote extensions for Org mode" '((emacs "28.1") (denote "4.0.0")) :commit "0151c5a8ef525ce523c7f6c42f0c49f731ad2ff5" :authors '(("Protesilaos Stavrou" . "info@protesilaos.com")) :maintainer '("Protesilaos Stavrou" . "info@protesilaos.com") :url "https://github.com/protesilaos/denote-org") blob - /dev/null blob + 80f75f9fe5adaaa334566230c050bacb3a5655bc (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/denote-org.el @@ -0,0 +1,853 @@ +;;; denote-org.el --- Denote extensions for Org mode -*- lexical-binding: t -*- + +;; Copyright (C) 2024-2025 Free Software Foundation, Inc. + +;; Author: Protesilaos Stavrou +;; Maintainer: Protesilaos Stavrou +;; URL: https://github.com/protesilaos/denote-org +;; Version: 0.1.1 +;; Package-Requires: ((emacs "28.1") (denote "4.0.0")) + +;; This file is NOT part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; Optional extensions to Denote that work specifically with Org mode. + +;;; Code: + +(require 'denote) +(require 'org) + +(defgroup denote-org () + "Optional extensions to Denote that work specifically with Org mode." + :group 'denote + :link '(info-link "(denote) top") + :link '(info-link "(denote-org) top") + :link '(url-link :tag "Denote homepage" "https://protesilaos.com/emacs/denote") + :link '(url-link :tag "Denote Org homepage" "https://protesilaos.com/emacs/denote-org")) + +;;;; Link to file and heading + +(defun denote-org--get-outline (file) + "Return `outline-regexp' headings and line numbers of FILE." + (with-current-buffer (find-file-noselect file) + (let ((outline-regexp (format "^\\(?:%s\\)" (or (bound-and-true-p outline-regexp) "\\*+ "))) + candidates) + (save-excursion + (goto-char (point-min)) + (while (if (bound-and-true-p outline-search-function) + (funcall outline-search-function) + (re-search-forward outline-regexp nil t)) + (push + ;; NOTE 2024-01-20: The -5 (minimum width) is a + ;; sufficiently high number to keep the alignment + ;; consistent in most cases. Larger files will simply + ;; shift the heading text in minibuffer, but this is not an + ;; issue anymore. + (format "%-5s %s" + (line-number-at-pos (point)) + (buffer-substring-no-properties (line-beginning-position) (line-end-position))) + candidates) + (goto-char (1+ (line-end-position))))) + (if candidates + (nreverse candidates) + (user-error "No outline"))))) + +(define-obsolete-function-alias + 'denote-org--outline-prompt + 'denote-org-outline-prompt + "3.1.0") + +(defun denote-org-outline-prompt (&optional file) + "Prompt for outline among headings retrieved by `denote-org--get-outline'. +With optional FILE use the outline of it, otherwise use that of +the current file." + (let ((current-file (or file buffer-file-name))) + (completing-read + (format "Select heading inside `%s': " + (propertize (file-name-nondirectory current-file) 'face 'denote-faces-prompt-current-name)) + (denote--completion-table-no-sort 'imenu (denote-org--get-outline current-file)) + nil :require-match))) + +(defun denote-org--get-heading-and-id-from-line (line file) + "Return heading text and CUSTOM_ID from the given LINE in FILE." + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (cons (denote-link-ol-get-heading) + (if (eq denote-org-store-link-to-heading 'context) + (org-entry-get (point) "CUSTOM_ID") + (denote-link-ol-get-id)))))) + +(defun denote-org-format-link-with-heading (file heading-id description &optional format) + "Prepare link to FILE with HEADING-ID using DESCRIPTION. +Optional FORMAT is the exact link pattern to use." + (when (region-active-p) + (setq description (buffer-substring-no-properties (region-beginning) (region-end))) + (denote--delete-active-region-content)) + (format + (or format "[[denote:%s::#%s][%s]]") + (denote-retrieve-filename-identifier file) + heading-id + description)) + +(define-obsolete-function-alias + 'denote-org-extras-link-to-heading + 'denote-org-link-to-heading + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-link-to-heading () + "Link to file and then specify a heading to extend the link to. + +The resulting link has the following pattern: + +[[denote:IDENTIFIER::#ORG-HEADING-CUSTOM-ID]][Description::Heading text]]. + +Because only Org files can have links to individual headings, +limit the list of possible files to those which include the .org +file extension (remember that Denote works with many file types, +per the user option `denote-file-type'). + +The user option `denote-org-store-link-to-heading' +determined whether the `org-store-link' function can save a link +to the current heading. Such links look the same as those of +this command, though the functionality defined herein is +independent of it. + +To only link to a file, use the `denote-link' command. + +Also see `denote-org-backlinks-for-heading'." + (declare (interactive-only t)) + (interactive nil org-mode) + (unless (derived-mode-p 'org-mode) + (user-error "Links to headings only work between Org files")) + (let ((context-p (eq denote-org-store-link-to-heading 'context))) + (when-let* ((file (denote-file-prompt ".*\\.org")) + (file-text (denote-get-link-description file)) + (heading (denote-org-outline-prompt file)) + (line (string-to-number (car (split-string heading "\t")))) + (heading-data (denote-org--get-heading-and-id-from-line line file)) + (heading-text (car heading-data)) + (heading-id (if (and context-p (null (cdr heading-data))) + heading-text + (cdr heading-data))) + (description (denote-link-format-heading-description file-text heading-text))) + (insert + (denote-org-format-link-with-heading + file + heading-id + description + (when (equal heading-text heading-id) + "[[denote:%s::*%s][%s]]")))))) + +;;;; Heading backlinks + +(defun denote-org--get-file-id-and-heading-id-or-context () + "Return link to current file and heading. +If a CUSTOM_ID is present and the value of the user option +`denote-org-store-link-to-heading' is set to `context', then return a +regexp that matches both the CUSTOM_ID and the context of the current +heading. This looks like: + + \\(ID::*HEADING-TEXT\\|ID::#HEADING-ID\\) + +If CUSTOM_ID is present but `denote-org-store-link-to-heading' is not +set to `context', then return a patternf of the following form: + + ID::#HEADING-ID" + (when-let* ((id (denote-retrieve-filename-identifier-with-error buffer-file-name))) + (let ((context-p (eq denote-org-store-link-to-heading 'context)) + (heading-id (org-entry-get (point) "CUSTOM_ID"))) + (cond + ((and context-p heading-id) + (format "\\(%s::%s%s\\|#%s\\)" id (shell-quote-argument "*") (denote-link-ol-get-heading) heading-id)) + (context-p + (concat id "::" (shell-quote-argument "*") (denote-link-ol-get-heading))) + (heading-id + (concat id "::#" heading-id)) + (t + (error "No way to get link to a heading at point in file `%s'" buffer-file-name)))))) + +(defun denote-org--get-backlinks-buffer-name (text) + "Format a buffer name for `denote-org-backlinks-for-heading' with TEXT." + (format "*Denote HEADING backlinks for %S*" text)) + +(defun denote-org--get-backlinks-for-heading (file-and-heading-id) + "Get backlinks to FILE-AND-HEADING-ID as a list of strings." + (when-let* ((files (denote-directory-files nil :omit-current :text-only)) + (xref-file-name-display 'abs) + (matches-in-files (xref-matches-in-files file-and-heading-id files)) + (xref-alist (xref--analyze matches-in-files))) + (mapcar + (lambda (x) + (denote-get-file-name-relative-to-denote-directory (car x))) + xref-alist))) + +(define-obsolete-function-alias + 'denote-org-extras-backlinks-for-heading + 'denote-org-backlinks-for-heading + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-backlinks-for-heading () + "Produce backlinks for the current heading. +This otherwise has the same behaviour as `denote-backlinks'---refer to +that for the details. + +Also see `denote-org-link-to-heading'." + (interactive) + (when-let* ((heading-id (denote-org--get-file-id-and-heading-id-or-context)) + (heading-text (substring-no-properties (denote-link-ol-get-heading)))) + (denote-link--prepare-backlinks heading-id ".*\\.org" (denote-org--get-backlinks-buffer-name heading-text)))) + +;;;; Extract subtree into its own note + +(defun denote-org--get-heading-date () + "Try to return a timestamp for the current Org heading. +This can be used as the value for the DATE argument of the +`denote' command." + (when-let* ((pos (point)) + (timestamp (or (org-entry-get pos "DATE") + (org-entry-get pos "CREATED") + (org-entry-get pos "CLOSED")))) + (date-to-time timestamp))) + +(define-obsolete-function-alias + 'denote-org-extras-extract-org-subtree + 'denote-org-extract-org-subtree + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-extract-org-subtree () + "Create new Denote note using the current Org subtree as input. +Remove the subtree from its current file and move its contents into a +new Denote file (a subtree is a heading with all of its contents, +including subheadings). + +Take the text of the subtree's top level heading and use it as the title +of the new note. + +If the heading has any tags, use them as the keywords of the new note. +If the Org file has any #+filetags use them as well (Org's filetags are +inherited by the headings). If none of these are true and the user +option `denote-prompts' includes an entry for keywords, then prompt for +keywords. Else do not include any keywords. + +If the heading has a PROPERTIES drawer, retain it for further review. + +If the heading's PROPERTIES drawer includes a DATE or CREATED property, +or there exists a CLOSED statement with a timestamp value, use that to +derive the date (or date and time) of the new note (if there is only a +date, the time is taken as 00:00). If more than one of these is +present, the order of preference is DATE, then CREATED, then CLOSED. If +none of these is present, use the current time. If the `denote-prompts' +includes an entry for a date, then prompt for a date at this stage (also +see `denote-date-prompt-use-org-read-date'). + +For the rest, consult the value of the user option `denote-prompts' in +the following scenaria: + +- Optionally prompt for a subdirectory, otherwise produce the new note + in the variable `denote-directory'. + +- Optionally prompt for a file signature, otherwise do not use one. + +Make the new note an Org file regardless of the value of the user option +`denote-file-type'." + (interactive nil org-mode) + (unless (derived-mode-p 'org-mode) + (user-error "Headings can only be extracted from Org files")) + (if-let* ((text (org-get-entry)) + (heading (denote-link-ol-get-heading))) + (let ((tags (org-get-tags)) + (date (denote-org--get-heading-date)) + subdirectory + signature) + (dolist (prompt denote-prompts) + (pcase prompt + ('keywords (when (not tags) (setq tags (denote-keywords-prompt)))) + ('subdirectory (setq subdirectory (denote-subdirectory-prompt))) + ('date (when (not date) (setq date (denote-date-prompt)))) + ('signature (setq signature (denote-signature-prompt))))) + (delete-region (org-entry-beginning-position) + (save-excursion (org-end-of-subtree t) (point))) + (denote heading tags 'org subdirectory date text signature)) + (user-error "No subtree to extract; aborting"))) + +;;;; Convert links from `:denote' to `:file' and vice versa + +(defun denote-org--get-link-type-regexp (type) + "Return regexp for Org link TYPE. +TYPE is a symbol of either `file' or `denote'. + +The regexp consists of four groups. Group 1 is the link type, 2 +is the target, 3 is the target's search terms, and 4 is the +description." + (let ((group-1)) + (pcase type + ('denote (setq group-1 "denote")) + ('file (setq group-1 "file")) + (_ (error "`%s' is an unknown link type" type))) + (format "\\[\\[\\(?1:%s:\\)\\(?:\\(?2:.*?\\)\\(?3:::.*\\)?\\]\\|\\]\\)\\(?4:\\[\\(?:.*?\\)\\]\\)?\\]" group-1))) + +(defun denote-org--get-path (id) + "Return file path to ID according to `org-link-file-path-type'." + (if (or (eq org-link-file-path-type 'adaptive) + (eq org-link-file-path-type 'relative)) + (denote-get-relative-path-by-id id) + (denote-get-path-by-id id))) + +(define-obsolete-function-alias + 'denote-org-extras-convert-links-to-file-type + 'denote-org-convert-links-to-file-type + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-convert-links-to-file-type () + "Convert denote: links to file: links in the current Org buffer. +Ignore all other link types. Also ignore links that do not +resolve to a file in the variable `denote-directory'." + (interactive nil org-mode) + (if (derived-mode-p 'org-mode) + (save-excursion + (let ((count 0)) + (goto-char (point-min)) + (while (re-search-forward (denote-org--get-link-type-regexp 'denote) nil :no-error) + (let* ((id (match-string-no-properties 2)) + (search (or (match-string-no-properties 3) "")) + (desc (or (match-string-no-properties 4) "")) + (file (save-match-data (denote-org--get-path id)))) + (when file + (let ((new-text (if desc + (format "[[file:%s%s]%s]" file search desc) + (format "[[file:%s%s]]" file search)))) + (replace-match new-text :fixed-case :literal) + (setq count (1+ count)))))) + (message "Converted %d `denote:' links to `file:' links" count))) + (user-error "The current file is not using Org mode"))) + +(define-obsolete-function-alias + 'denote-org-extras-convert-links-to-denote-type + 'denote-org-convert-links-to-denote-type + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-convert-links-to-denote-type () + "Convert file: links to denote: links in the current Org buffer. +Ignore all other link types. Also ignore file: links that do not +point to a file with a Denote file name." + (interactive nil org-mode) + (if (derived-mode-p 'org-mode) + (save-excursion + (let ((count 0)) + (goto-char (point-min)) + (while (re-search-forward (denote-org--get-link-type-regexp 'file) nil :no-error) + (let* ((file (match-string-no-properties 2)) + (search (or (match-string-no-properties 3) "")) + (desc (or (match-string-no-properties 4) "")) + (id (save-match-data (denote-retrieve-filename-identifier file)))) + (when id + (let ((new-text (if desc + (format "[[denote:%s%s]%s]" id search desc) + (format "[[denote:%s%s]]" id search)))) + (replace-match new-text :fixed-case :literal) + (setq count (1+ count)))))) + (message "Converted %d `file:' links to `denote:' links" count))) + (user-error "The current file is not using Org mode"))) + +;;;; Org dynamic blocks + +;; NOTE 2024-01-22 12:26:13 +0200: The following is copied from the +;; now-deleted denote-org-dblock.el. Its original author was Elias +;; Storms , with substantial contributions and +;; further developments by me (Protesilaos). + +;; This section defines Org dynamic blocks using the facility described +;; in the Org manual. Evaluate this: +;; +;; (info "(org) Dynamic Blocks") +;; +;; The dynamic blocks defined herein are documented at length in the +;; Denote manual. See the following node and its subsections: +;; +;; (info "(denote) Use Org dynamic blocks") + +;;;;; Common helper functions + +(defun denote-org-dblock--files (files-matching-regexp &optional sort-by-component reverse exclude-regexp) + "Return list of FILES-MATCHING-REGEXP in variable `denote-directory'. +SORT-BY-COMPONENT, REVERSE, EXCLUDE-REGEXP have the same meaning as +`denote-sort-get-directory-files'. If both are nil, do not try to +perform any sorting. + +Also see `denote-org-dblock--files-missing-only'." + (cond + ((and sort-by-component reverse) + (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse :omit-current exclude-regexp)) + (sort-by-component + (denote-sort-get-directory-files files-matching-regexp sort-by-component nil :omit-current exclude-regexp)) + (reverse + (denote-sort-get-directory-files files-matching-regexp :no-component-specified reverse :omit-current exclude-regexp)) + (t + (denote-directory-files files-matching-regexp :omit-current nil exclude-regexp)))) + +(defun denote-org-dblock--get-missing-links (regexp) + "Return list of missing links to all notes matching REGEXP. +Missing links are those for which REGEXP does not have a match in +the current buffer." + (let ((found-files (denote-directory-files regexp :omit-current)) + (linked-files (denote-link--expand-identifiers denote-org-link-in-context-regexp))) + (if-let* ((final-files (seq-difference found-files linked-files))) + final-files + (message "All links matching `%s' are present" regexp) + '()))) + +(defun denote-org-dblock--files-missing-only (files-matching-regexp &optional sort-by-component reverse) + "Return list of missing links to FILES-MATCHING-REGEXP. +SORT-BY-COMPONENT and REVERSE have the same meaning as +`denote-sort-files'. If both are nil, do not try to perform any +sorting. + +Also see `denote-org-dblock--files'." + (denote-sort-files + (denote-org-dblock--get-missing-links files-matching-regexp) + sort-by-component + reverse)) + +;;;;; Dynamic block to insert links + +(define-obsolete-function-alias + 'denote-org-extras-dblock-insert-links + 'denote-org-dblock-insert-links + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-dblock-insert-links (regexp) + "Create Org dynamic block to insert Denote links matching REGEXP." + (interactive + (list + (denote-files-matching-regexp-prompt)) + org-mode) + (org-create-dblock (list :name "denote-links" + :regexp regexp + :not-regexp nil + :excluded-dirs-regexp nil + :sort-by-component nil + :reverse-sort nil + :id-only nil + :include-date nil)) + (org-update-dblock)) + +;; NOTE 2024-03-30: This is how the autoload is done in org.el. +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "denote-links" 'denote-org-dblock-insert-links))) + +;; TODO 2024-12-04: Maybe we can do this for anything that deals with +;; regular expressions that users provide? I prefer not to do the +;; work if nobody wants it, though I am mentioning this here just in +;; case. +(defun denote-org--parse-rx (regexp) + "Parse REGEXP as an `rx' argument or string and return string." + (cond + ((null regexp) + nil) + ((listp regexp) + (rx-to-string regexp)) + ((stringp regexp) + regexp) + (t + (error "Regexp `%s' is neither a list nor a string" regexp)))) + +;;;###autoload +(defun org-dblock-write:denote-links (params) + "Function to update `denote-links' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block." + (let* ((rx (denote-org--parse-rx (plist-get params :regexp))) + (not-rx (denote-org--parse-rx (plist-get params :not-regexp))) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (include-date (plist-get params :include-date)) + (block-name (plist-get params :block-name)) + (denote-excluded-directories-regexp (or (plist-get params :excluded-dirs-regexp) + denote-excluded-directories-regexp)) + (files (denote-org-dblock--files rx sort reverse not-rx))) + (when block-name (insert "#+name: " block-name "\n")) + (denote-link--insert-links files 'org (plist-get params :id-only) :no-other-sorting include-date) + (join-line))) ; remove trailing empty line + +;;;;; Dynamic block to insert missing links + +;; TODO 2024-12-03: Do we need the :not-regexp here? I think yes, +;; though I prefer to have a user of this kind of dblock send me their +;; feedback. + +(define-obsolete-function-alias + 'denote-org-extras-dblock-insert-missing-links + 'denote-org-dblock-insert-missing-links + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-dblock-insert-missing-links (regexp) + "Create Org dynamic block to insert Denote links matching REGEXP." + (interactive + (list + (denote-files-matching-regexp-prompt)) + org-mode) + (org-create-dblock (list :name "denote-missing-links" + :regexp regexp + :excluded-dirs-regexp nil + :sort-by-component nil + :reverse-sort nil + :id-only nil + :include-date nil)) + (org-update-dblock)) + +;; NOTE 2024-03-30: This is how the autoload is done in org.el. +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "denote-missing-links" 'denote-org-dblock-insert-missing-links))) + +;;;###autoload +(defun org-dblock-write:denote-missing-links (params) + "Function to update `denote-links' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block." + (let* ((rx (denote-org--parse-rx (plist-get params :regexp))) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (include-date (plist-get params :include-date)) + (block-name (plist-get params :block-name)) + (denote-excluded-directories-regexp (or (plist-get params :excluded-dirs-regexp) + denote-excluded-directories-regexp)) + (files (denote-org-dblock--files-missing-only rx sort reverse))) + (when block-name (insert "#+name: " block-name "\n")) + (denote-link--insert-links files 'org (plist-get params :id-only) :no-other-sorting include-date) + (join-line))) ; remove trailing empty line + +;;;;; Dynamic block to insert backlinks + +(defun denote-org-dblock--maybe-sort-backlinks (files sort-by-component reverse) + "Sort backlink FILES if SORT-BY-COMPONENT and/or REVERSE is non-nil." + (cond + ((and sort-by-component reverse) + (denote-sort-files files sort-by-component reverse)) + (sort-by-component + (denote-sort-files files sort-by-component)) + (reverse + (denote-sort-files files :no-component-specified reverse)) + (t + files))) + +;; TODO 2024-12-03: Do we need the :not-regexp here? I think yes, +;; though I prefer to have a user of this kind of dblock send me their +;; feedback. + +(define-obsolete-function-alias + 'denote-org-extras-dblock-insert-backlinks + 'denote-org-dblock-insert-backlinks + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-dblock-insert-backlinks () + "Create Org dynamic block to insert Denote backlinks to current file." + (interactive nil org-mode) + (org-create-dblock (list :name "denote-backlinks" + :excluded-dirs-regexp nil + :sort-by-component nil + :reverse-sort nil + :id-only nil + :this-heading-only nil + :include-date nil)) + (org-update-dblock)) + +;; NOTE 2024-03-30: This is how the autoload is done in org.el. +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "denote-backlinks" 'denote-org-dblock-insert-backlinks))) + +;;;###autoload +(defun org-dblock-write:denote-backlinks (params) + "Function to update `denote-backlinks' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block." + (when-let* ((files (if (plist-get params :this-heading-only) + (denote-org--get-backlinks-for-heading (denote-org--get-file-id-and-heading-id-or-context)) + (denote-link-return-backlinks)))) + (let* ((sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (include-date (plist-get params :include-date)) + (denote-excluded-directories-regexp (or (plist-get params :excluded-dirs-regexp) + denote-excluded-directories-regexp)) + (files (denote-org-dblock--maybe-sort-backlinks files sort reverse))) + (denote-link--insert-links files 'org (plist-get params :id-only) :no-other-sorting include-date) + (join-line)))) ; remove trailing empty line + +;;;;; Dynamic block to insert entire file contents + +(defun denote-org-dblock--get-file-contents (file &optional no-front-matter add-links) + "Insert the contents of FILE. +With optional NO-FRONT-MATTER as non-nil, try to remove the front +matter from the top of the file. If NO-FRONT-MATTER is a number, +remove that many lines starting from the top. If it is any other +non-nil value, delete from the top until the first blank line. + +With optional ADD-LINKS as non-nil, first insert a link to the +file and then insert its contents. In this case, format the +contents as a typographic list. If ADD-LINKS is `id-only', then +insert links as `denote-link' does when supplied with an ID-ONLY +argument." + (when (denote-file-is-note-p file) + (with-temp-buffer + (when add-links + (insert + (format "- %s\n\n" + (denote-format-link + file + (denote-get-link-description file) + 'org + (eq add-links 'id-only))))) + (let ((beginning-of-contents (point))) + (insert-file-contents file) + (when no-front-matter + (delete-region + (if (natnump no-front-matter) + (progn (forward-line no-front-matter) (line-beginning-position)) + (1+ (re-search-forward "^$" nil :no-error 1))) + beginning-of-contents)) + (when add-links + (indent-region beginning-of-contents (point-max) 2))) + (buffer-string)))) + +(defvar denote-org-dblock-file-contents-separator + (concat "\n\n" (make-string 50 ?-) "\n\n\n") + "Fallback separator used by `denote-org-dblock-add-files'.") + +(defun denote-org-dblock--separator (separator) + "Return appropriate value of SEPARATOR for `denote-org-dblock-add-files'." + (cond + ((null separator) "") + ((stringp separator) separator) + (t denote-org-dblock-file-contents-separator))) + +(defun denote-org-dblock-add-files (regexp &optional separator no-front-matter add-links sort-by-component reverse excluded-dirs-regexp exclude-regexp) + "Insert files matching REGEXP. + +Seaprate them with the optional SEPARATOR. If SEPARATOR is nil, +use the `denote-org-dblock-file-contents-separator'. + +If optional NO-FRONT-MATTER is non-nil try to remove the front +matter from the top of the file. Do it by finding the first +blank line, starting from the top of the buffer. + +If optional ADD-LINKS is non-nil, first insert a link to the file +and then insert its contents. In this case, format the contents +as a typographic list. + +If optional SORT-BY-COMPONENT is a symbol among `denote-sort-components', +sort files matching REGEXP by the corresponding Denote file name +component. If the symbol is not among `denote-sort-components', +fall back to the default identifier-based sorting. + +If optional REVERSE is non-nil reverse the sort order. + +Optional EXCLUDED-DIRS-REGEXP is the `let' bound value of +`denote-excluded-directories-regexp'. When nil, the original value of +that user option is used. + +Optional EXCLUDE-REGEXP is a more general way to exclude files whose +name matches the given regular expression." + (let* ((denote-excluded-directories-regexp (or excluded-dirs-regexp denote-excluded-directories-regexp)) + (files (denote-org-dblock--files regexp sort-by-component reverse exclude-regexp)) + (files-contents (mapcar + (lambda (file) (denote-org-dblock--get-file-contents file no-front-matter add-links)) + files))) + (insert (string-join files-contents (denote-org-dblock--separator separator))))) + +(define-obsolete-function-alias + 'denote-org-extras-dblock-insert-files + 'denote-org-dblock-insert-files + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-dblock-insert-files (regexp sort-by-component) + "Create Org dynamic block to insert Denote files matching REGEXP. +Sort the files according to SORT-BY-COMPONENT, which is a symbol +among `denote-sort-components'." + (interactive + (list + (denote-files-matching-regexp-prompt) + (denote-sort-component-prompt)) + org-mode) + (org-create-dblock (list :name "denote-files" + :regexp regexp + :not-regexp nil + :excluded-dirs-regexp nil + :sort-by-component sort-by-component + :reverse-sort nil + :no-front-matter nil + :file-separator nil + :add-links nil)) + (org-update-dblock)) + +;; NOTE 2024-03-30: This is how the autoload is done in org.el. +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "denote-files" 'denote-org-dblock-insert-files))) + +;;;###autoload +(defun org-dblock-write:denote-files (params) + "Function to update `denote-files' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block." + (let* ((rx (denote-org--parse-rx (plist-get params :regexp))) + (not-rx (denote-org--parse-rx (plist-get params :not-regexp))) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (block-name (plist-get params :block-name)) + (separator (plist-get params :file-separator)) + (no-f-m (plist-get params :no-front-matter)) + (add-links (plist-get params :add-links)) + (excluded-dirs (plist-get params :excluded-dirs-regexp))) + (when block-name (insert "#+name: " block-name "\n")) + (when rx (denote-org-dblock-add-files rx separator no-f-m add-links sort reverse excluded-dirs not-rx))) + (join-line)) ; remove trailing empty line + +;;;; Insert files as headings + +(defun denote-org-dblock--extract-regexp (regexp) + "Extract REGEXP from the buffer and trim it of surrounding spaces." + (string-trim + (save-excursion + (re-search-forward regexp nil :no-error) + (buffer-substring-no-properties (match-end 0) (line-end-position))))) + +(defun denote-org-dblock--get-file-contents-as-heading (file add-links) + "Insert the contents of Org FILE, formatting the #+title as a heading. +With optional ADD-LINKS, make the title link to the original file." + (when-let* (((denote-file-is-note-p file)) + (identifier (denote-retrieve-filename-identifier file)) + (file-type (denote-filetype-heuristics file)) + ((eq file-type 'org))) + (with-temp-buffer + (let ((beginning-of-contents (point)) + title + tags) + (insert-file-contents file) + (setq title (denote-org-dblock--extract-regexp (denote--title-key-regexp file-type))) + (setq tags (denote-org-dblock--extract-regexp (denote--keywords-key-regexp file-type))) + (delete-region (1+ (re-search-forward "^$" nil :no-error 1)) beginning-of-contents) + (goto-char beginning-of-contents) + (when (and title tags) + (if add-links + (insert (format "* [[denote:%s][%s]] %s\n\n" identifier title tags)) + (insert (format "* %s %s\n\n" title tags))) + (org-align-tags :all)) + (while (re-search-forward "^\\(*+?\\) " nil :no-error) + (replace-match (format "*%s " "\\1")))) + (buffer-string)))) + +(defun denote-org-dblock-add-files-as-headings (regexp &optional add-links sort-by-component reverse excluded-dirs-regexp exclude-regexp) + "Insert files matching REGEXP. + +If optional ADD-LINKS is non-nil, first insert a link to the file +and then insert its contents. In this case, format the contents +as a typographic list. + +If optional SORT-BY-COMPONENT is a symbol among `denote-sort-components', +sort files matching REGEXP by the corresponding Denote file name +component. If the symbol is not among `denote-sort-components', +fall back to the default identifier-based sorting. + +If optional REVERSE is non-nil reverse the sort order. + +Optional EXCLUDED-DIRS-REGEXP is the `let' bound value of +`denote-excluded-directories-regexp'. When nil, the original value of +that user option is used. + +Optional EXCLUDE-REGEXP is a more general way to exclude files whose +name matches the given regular expression." + (let* ((denote-excluded-directories-regexp (or excluded-dirs-regexp denote-excluded-directories-regexp)) + (files (denote-org-dblock--files regexp sort-by-component reverse exclude-regexp)) + (files-contents (mapcar + (lambda (file) + (denote-org-dblock--get-file-contents-as-heading file add-links)) + files))) + (insert (string-join files-contents)))) + +(define-obsolete-function-alias + 'denote-org-extras-dblock-insert-files-as-headings + 'denote-org-dblock-insert-files-as-headings + "As part of making `denote-org' a standalone package") + +;;;###autoload +(defun denote-org-dblock-insert-files-as-headings (regexp sort-by-component) + "Create Org dynamic block to insert Denote Org files matching REGEXP. + +Turn the #+title of each file into a top-level heading. Then increment +all original headings in the file by one, so that they become +subheadings of what once was the #+title. + +Use the #+filetags of each file as tags for the top-level heading (what +was the #+title). + +Sort the files according to SORT-BY-COMPONENT, which is a symbol +among `denote-sort-components'. + +IMPORTANT NOTE: This dynamic block only works with Org files, because it +has to assume the Org notation in order to insert each file's contents +as its own heading." + (interactive + (list + (denote-files-matching-regexp-prompt) + (denote-sort-component-prompt)) + org-mode) + (org-create-dblock (list :name "denote-files-as-headings" + :regexp regexp + :not-regexp nil + :excluded-dirs-regexp nil + :sort-by-component sort-by-component + :reverse-sort nil + :add-links nil)) + (org-update-dblock)) + +;; NOTE 2024-03-30: This is how the autoload is done in org.el. +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "denote-files-as-headings" 'denote-org-dblock-insert-files-as-headings))) + +;;;###autoload +(defun org-dblock-write:denote-files-as-headings (params) + "Function to update `denote-files' Org Dynamic blocks. +Used by `org-dblock-update' with PARAMS provided by the dynamic block." + (let* ((rx (denote-org--parse-rx (plist-get params :regexp))) + (not-rx (denote-org--parse-rx (plist-get params :not-regexp))) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (block-name (plist-get params :block-name)) + (add-links (plist-get params :add-links)) + (excluded-dirs (plist-get params :excluded-dirs-regexp))) + (when block-name (insert "#+name: " block-name "\n")) + (when rx (denote-org-dblock-add-files-as-headings rx add-links sort reverse excluded-dirs not-rx))) + (join-line)) ; remove trailing empty line + +(provide 'denote-org) +;;; denote-org.el ends here blob - /dev/null blob + f1fba7dac575b855475a6aecc1201ae5fcd2b7f1 (mode 644) Binary files /dev/null and elpa/denote-org-0.1.1/denote-org.info differ blob - /dev/null blob + cf277daac78585fb73dccd9fcd5fed9303d556cc (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1/dir @@ -0,0 +1,18 @@ +This is the file .../info/dir, which contains the +topmost node of the Info hierarchy, called (dir)Top. +The first time you invoke Info you start off looking at this node. + +File: dir, Node: Top This is the top of the INFO tree + + This (the Directory node) gives a menu of major topics. + Typing "q" exits, "H" lists all Info commands, "d" returns here, + "h" gives a primer for first-timers, + "mEmacs" visits the Emacs manual, etc. + + In Emacs, you can click mouse button 2 on a menu item or cross reference + to select it. + +* Menu: + +Emacs misc features +* Denote Org: (denote-org). Extensions to better integrate Org with Denote. blob - /dev/null blob + 707df4f9bdce70fe9deb2cf693b05bdfe88c6e4f (mode 644) --- /dev/null +++ elpa/denote-org-0.1.1.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2025-04-19T23:10:04+0200 using EDDSA \ No newline at end of file blob - 5fcafacee76da44024a76a8368c10e684803ca8b blob + b951843615cd8df89ec49b6397307b79232e5120 --- init.el +++ init.el @@ -3,6 +3,10 @@ (require 'xdg) (require 'cl-lib) +(setq lh/dir-documents + (expand-file-name + (cond ((eq system-type 'gnu/linux) (or (xdg-user-dir "DOCUMENTS") "~/Documents")) + ((eq system-type 'windows-nt) "~/Documents")))) (setq lh/dir-data-home (expand-file-name (cond ((eq system-type 'gnu/linux) (or (xdg-data-home) "~/.local/share")) @@ -32,8 +36,9 @@ ("melpa-stable" . 1) ("melpa" . 0)) package-pinned-packages '((sly . "melpa")) - package-selected-packages '(csv-mode elfeed magit nhexl-mode notmuch ob-restclient paredit - paredit-menu restclient restclient-jq sly)) + package-selected-packages '(csv-mode denote denote-org elfeed magit nhexl-mode notmuch + ob-restclient paredit paredit-menu restclient + restclient-jq sly)) (package-initialize) ;;;; Completions @@ -102,8 +107,17 @@ (with-eval-after-load 'restclient (require 'restclient-capf)) +;;;; org + denote +(let* ((org-dir (expand-file-name "org" lh/dir-documents)) + (denote-dir (expand-file-name "notes" org-dir))) + (make-directory org-dir t) + (make-directory denote-dir t) + (setopt org-directory org-dir + denote-directory denote-dir)) + ;;;; Misc (setopt focus-follows-mouse t + mouse-autoselect-window 0.2 frame-resize-pixelwise t scroll-conservatively 100 pixel-scroll-precision-mode t